chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -29,27 +29,12 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
const backupHome = path.join(userdataDir, 'Backups');
const workspacesJsonPath = path.join(backupHome, 'workspaces.json');
const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace');
const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource));
const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo');
const customFile = URI.parse('customScheme://some/path');
const customFileWithFragment = URI.parse('customScheme2://some/path#fragment');
const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar');
const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar');
const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
import { insert } from 'vs/base/common/arrays';
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(backupPath: string) {
super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': userdataDir }, TestProductService);
constructor(testDir: string, backupPath: string) {
super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': testDir }, TestProductService);
}
}
@@ -60,9 +45,10 @@ export class NodeTestBackupFileService extends NativeBackupFileService {
private backupResourceJoiners: Function[];
private discardBackupJoiners: Function[];
discardedBackups: URI[];
private pendingBackupsArr: Promise<void>[];
constructor(workspaceBackupPath: string) {
const environmentService = new TestWorkbenchEnvironmentService(workspaceBackupPath);
constructor(testDir: string, workspaceBackupPath: string) {
const environmentService = new TestWorkbenchEnvironmentService(testDir, workspaceBackupPath);
const logService = new NullLogService();
const fileService = new FileService(logService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
@@ -75,6 +61,11 @@ export class NodeTestBackupFileService extends NativeBackupFileService {
this.backupResourceJoiners = [];
this.discardBackupJoiners = [];
this.discardedBackups = [];
this.pendingBackupsArr = [];
}
async waitForAllBackups(): Promise<void> {
await Promise.all(this.pendingBackupsArr);
}
joinBackupResource(): Promise<void> {
@@ -82,7 +73,14 @@ export class NodeTestBackupFileService extends NativeBackupFileService {
}
async backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: any, token?: CancellationToken): Promise<void> {
await super.backup(resource, content, versionId, meta, token);
const p = super.backup(resource, content, versionId, meta, token);
const removeFromPendingBackups = insert(this.pendingBackupsArr, p.then(undefined, undefined));
try {
await p;
} finally {
removeFromPendingBackups();
}
while (this.backupResourceJoiners.length) {
this.backupResourceJoiners.pop()!();
@@ -112,20 +110,43 @@ export class NodeTestBackupFileService extends NativeBackupFileService {
}
suite('BackupFileService', () => {
let testDir: string;
let backupHome: string;
let workspacesJsonPath: string;
let workspaceBackupPath: string;
let fooBackupPath: string;
let barBackupPath: string;
let untitledBackupPath: string;
let service: NodeTestBackupFileService;
setup(async () => {
service = new NodeTestBackupFileService(workspaceBackupPath);
let workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace');
let fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo');
let customFile = URI.parse('customScheme://some/path');
let customFileWithFragment = URI.parse('customScheme2://some/path#fragment');
let barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar');
let fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar');
let untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
setup(async () => {
testDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
backupHome = path.join(testDir, 'Backups');
workspacesJsonPath = path.join(backupHome, 'workspaces.json');
workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource));
fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
service = new NodeTestBackupFileService(testDir, workspaceBackupPath);
// Delete any existing backups completely and then re-create it.
await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
await pfs.mkdirp(backupHome);
return pfs.writeFile(workspacesJsonPath, '');
});
teardown(() => {
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
return pfs.rimraf(testDir);
});
suite('hashPath', () => {
@@ -136,8 +157,8 @@ suite('BackupFileService', () => {
});
const actual = hashPath(uri);
// If these hashes change people will lose their backed up files!
assert.equal(actual, '13264068d108c6901b3592ea654fcd57');
assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
assert.strictEqual(actual, '13264068d108c6901b3592ea654fcd57');
assert.strictEqual(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
});
test('should correctly hash the path for file scheme URIs', () => {
@@ -145,11 +166,11 @@ suite('BackupFileService', () => {
const actual = hashPath(uri);
// If these hashes change people will lose their backed up files!
if (platform.isWindows) {
assert.equal(actual, 'dec1a583f52468a020bd120c3f01d812');
assert.strictEqual(actual, 'dec1a583f52468a020bd120c3f01d812');
} else {
assert.equal(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf');
assert.strictEqual(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf');
}
assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
assert.strictEqual(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex'));
});
});
@@ -160,7 +181,7 @@ suite('BackupFileService', () => {
const workspaceHash = hashPath(workspaceResource);
const filePathHash = hashPath(backupResource);
const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
assert.strictEqual(service.toBackupResource(backupResource).toString(), expectedPath);
});
test('should get the correct backup path for untitled files', () => {
@@ -169,49 +190,49 @@ suite('BackupFileService', () => {
const workspaceHash = hashPath(workspaceResource);
const filePathHash = hashPath(backupResource);
const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString();
assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
assert.strictEqual(service.toBackupResource(backupResource).toString(), expectedPath);
});
});
suite('backup', () => {
test('no text', async () => {
await service.backup(fooFile);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n`);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\n`);
assert.ok(service.hasBackupSync(fooFile));
});
test('text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`);
assert.ok(service.hasBackupSync(fooFile));
});
test('text file (with version)', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), 666);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 666);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`);
assert.ok(!service.hasBackupSync(fooFile, 555));
assert.ok(service.hasBackupSync(fooFile, 666));
});
test('text file (with meta)', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), undefined, { etag: '678', orphaned: true });
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`);
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), undefined, { etag: '678', orphaned: true });
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`);
assert.ok(service.hasBackupSync(fooFile));
});
test('untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.strictEqual(fs.existsSync(untitledBackupPath), true);
assert.strictEqual(fs.readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest`);
assert.ok(service.hasBackupSync(untitledFile));
});
@@ -219,9 +240,9 @@ suite('BackupFileService', () => {
const model = createTextModel('test');
await service.backup(fooFile, model.createSnapshot());
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest`);
assert.ok(service.hasBackupSync(fooFile));
model.dispose();
@@ -231,9 +252,9 @@ suite('BackupFileService', () => {
const model = createTextModel('test');
await service.backup(untitledFile, model.createSnapshot());
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.strictEqual(fs.existsSync(untitledBackupPath), true);
assert.strictEqual(fs.readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest`);
model.dispose();
});
@@ -243,9 +264,9 @@ suite('BackupFileService', () => {
const model = createTextModel(largeString);
await service.backup(fooFile, model.createSnapshot());
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n${largeString}`);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.strictEqual(fs.existsSync(fooBackupPath), true);
assert.strictEqual(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\n${largeString}`);
assert.ok(service.hasBackupSync(fooFile));
model.dispose();
@@ -256,9 +277,9 @@ suite('BackupFileService', () => {
const model = createTextModel(largeString);
await service.backup(untitledFile, model.createSnapshot());
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\n${largeString}`);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.strictEqual(fs.existsSync(untitledBackupPath), true);
assert.strictEqual(fs.readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\n${largeString}`);
assert.ok(service.hasBackupSync(untitledFile));
model.dispose();
@@ -270,79 +291,79 @@ suite('BackupFileService', () => {
cts.cancel();
await promise;
assert.equal(fs.existsSync(fooBackupPath), false);
assert.strictEqual(fs.existsSync(fooBackupPath), false);
assert.ok(!service.hasBackupSync(fooFile));
});
});
suite('discardBackup', () => {
test('text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.ok(service.hasBackupSync(fooFile));
await service.discardBackup(fooFile);
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
assert.strictEqual(fs.existsSync(fooBackupPath), false);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
assert.ok(!service.hasBackupSync(fooFile));
});
test('untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
await service.discardBackup(untitledFile);
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
assert.strictEqual(fs.existsSync(untitledBackupPath), false);
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
});
});
suite('discardBackups', () => {
test('text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
await service.discardBackups();
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.existsSync(barBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
assert.strictEqual(fs.existsSync(fooBackupPath), false);
assert.strictEqual(fs.existsSync(barBackupPath), false);
assert.strictEqual(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
});
test('untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
await service.discardBackups();
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
assert.strictEqual(fs.existsSync(untitledBackupPath), false);
assert.strictEqual(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
});
test('can backup after discarding all', async () => {
await service.discardBackups();
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.existsSync(workspaceBackupPath), true);
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
assert.strictEqual(fs.existsSync(workspaceBackupPath), true);
});
});
suite('getBackups', () => {
test('("file") - text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
const textFiles = await service.getBackups();
assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.deepStrictEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
const textFiles_1 = await service.getBackups();
assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
assert.deepStrictEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
});
test('("file") - untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
const textFiles = await service.getBackups();
assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
assert.deepStrictEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
});
test('("untitled") - untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false));
const textFiles = await service.getBackups();
assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
assert.deepStrictEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
});
});
@@ -451,10 +472,10 @@ suite('BackupFileService', () => {
orphaned: false
};
await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
await service.backup(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta);
const fileContents = fs.readFileSync(fooBackupPath).toString();
assert.equal(fileContents.indexOf(fooFile.toString()), 0);
assert.strictEqual(fileContents.indexOf(fooFile.toString()), 0);
const metaIndex = fileContents.indexOf('{');
const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex);
@@ -462,7 +483,7 @@ suite('BackupFileService', () => {
const backup = await service.resolve(fooFile);
assert.ok(backup);
assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true)));
assert.strictEqual(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true)));
assert.ok(!backup!.meta);
});
@@ -523,7 +544,7 @@ suite('BackupFileService', () => {
test('should ignore invalid backups', async () => {
const contents = 'test\nand more stuff';
await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1);
await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1);
const backup = await service.resolve(fooBarFile);
if (!backup) {
@@ -547,115 +568,100 @@ suite('BackupFileService', () => {
expectedMeta = meta;
}
await service.backup(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta);
await service.backup(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).textBuffer.createSnapshot(false), 1, meta);
const backup = await service.resolve<IBackupTestMetaData>(resource);
assert.ok(backup);
assert.equal(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true)));
assert.strictEqual(contents, snapshotToString(backup!.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(true)));
if (expectedMeta) {
assert.equal(backup!.meta!.etag, expectedMeta.etag);
assert.equal(backup!.meta!.size, expectedMeta.size);
assert.equal(backup!.meta!.mtime, expectedMeta.mtime);
assert.equal(backup!.meta!.orphaned, expectedMeta.orphaned);
assert.strictEqual(backup!.meta!.etag, expectedMeta.etag);
assert.strictEqual(backup!.meta!.size, expectedMeta.size);
assert.strictEqual(backup!.meta!.mtime, expectedMeta.mtime);
assert.strictEqual(backup!.meta!.orphaned, expectedMeta.orphaned);
} else {
assert.ok(!backup!.meta);
}
}
});
});
suite('BackupFilesModel', () => {
let service: NodeTestBackupFileService;
setup(async () => {
service = new NodeTestBackupFileService(workspaceBackupPath);
// Delete any existing backups completely and then re-create it.
await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
await pfs.mkdirp(backupHome);
return pfs.writeFile(workspacesJsonPath, '');
});
teardown(() => {
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
});
test('simple', () => {
const model = new BackupFilesModel(service.fileService);
const resource1 = URI.file('test.html');
assert.equal(model.has(resource1), false);
model.add(resource1);
assert.equal(model.has(resource1), true);
assert.equal(model.has(resource1, 0), true);
assert.equal(model.has(resource1, 1), false);
assert.equal(model.has(resource1, 1, { foo: 'bar' }), false);
model.remove(resource1);
assert.equal(model.has(resource1), false);
model.add(resource1);
assert.equal(model.has(resource1), true);
assert.equal(model.has(resource1, 0), true);
assert.equal(model.has(resource1, 1), false);
model.clear();
assert.equal(model.has(resource1), false);
model.add(resource1, 1);
assert.equal(model.has(resource1), true);
assert.equal(model.has(resource1, 0), false);
assert.equal(model.has(resource1, 1), true);
const resource2 = URI.file('test1.html');
const resource3 = URI.file('test2.html');
const resource4 = URI.file('test3.html');
model.add(resource2);
model.add(resource3);
model.add(resource4, undefined, { foo: 'bar' });
assert.equal(model.has(resource1), true);
assert.equal(model.has(resource2), true);
assert.equal(model.has(resource3), true);
assert.equal(model.has(resource4), true);
assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true);
assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false);
});
test('resolve', async () => {
await pfs.mkdirp(path.dirname(fooBackupPath));
fs.writeFileSync(fooBackupPath, 'foo');
const model = new BackupFilesModel(service.fileService);
const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true);
});
test('get', () => {
const model = new BackupFilesModel(service.fileService);
assert.deepEqual(model.get(), []);
const file1 = URI.file('/root/file/foo.html');
const file2 = URI.file('/root/file/bar.html');
const untitled = URI.file('/root/untitled/bar.html');
model.add(file1);
model.add(file2);
model.add(untitled);
assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
});
suite('BackupFilesModel', () => {
test('simple', () => {
const model = new BackupFilesModel(service.fileService);
const resource1 = URI.file('test.html');
assert.strictEqual(model.has(resource1), false);
model.add(resource1);
assert.strictEqual(model.has(resource1), true);
assert.strictEqual(model.has(resource1, 0), true);
assert.strictEqual(model.has(resource1, 1), false);
assert.strictEqual(model.has(resource1, 1, { foo: 'bar' }), false);
model.remove(resource1);
assert.strictEqual(model.has(resource1), false);
model.add(resource1);
assert.strictEqual(model.has(resource1), true);
assert.strictEqual(model.has(resource1, 0), true);
assert.strictEqual(model.has(resource1, 1), false);
model.clear();
assert.strictEqual(model.has(resource1), false);
model.add(resource1, 1);
assert.strictEqual(model.has(resource1), true);
assert.strictEqual(model.has(resource1, 0), false);
assert.strictEqual(model.has(resource1, 1), true);
const resource2 = URI.file('test1.html');
const resource3 = URI.file('test2.html');
const resource4 = URI.file('test3.html');
model.add(resource2);
model.add(resource3);
model.add(resource4, undefined, { foo: 'bar' });
assert.strictEqual(model.has(resource1), true);
assert.strictEqual(model.has(resource2), true);
assert.strictEqual(model.has(resource3), true);
assert.strictEqual(model.has(resource4), true);
assert.strictEqual(model.has(resource4, undefined, { foo: 'bar' }), true);
assert.strictEqual(model.has(resource4, undefined, { bar: 'foo' }), false);
});
test('resolve', async () => {
await pfs.mkdirp(path.dirname(fooBackupPath));
fs.writeFileSync(fooBackupPath, 'foo');
const model = new BackupFilesModel(service.fileService);
const resolvedModel = await model.resolve(URI.file(workspaceBackupPath));
assert.strictEqual(resolvedModel.has(URI.file(fooBackupPath)), true);
});
test('get', () => {
const model = new BackupFilesModel(service.fileService);
assert.deepStrictEqual(model.get(), []);
const file1 = URI.file('/root/file/foo.html');
const file2 = URI.file('/root/file/bar.html');
const untitled = URI.file('/root/untitled/bar.html');
model.add(file1);
model.add(file2);
model.add(untitled);
assert.deepStrictEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]);
});
});
});

View File

@@ -3,8 +3,65 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
import { BrowserClipboardService as BaseBrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { once } from 'vs/base/common/functional';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class BrowserClipboardService extends BaseBrowserClipboardService {
constructor(
@INotificationService private readonly notificationService: INotificationService,
@IOpenerService private readonly openerService: IOpenerService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
}
async readText(type?: string): Promise<string> {
if (type) {
return super.readText(type);
}
try {
return await navigator.clipboard.readText();
} catch (error) {
if (!!this.environmentService.extensionTestsLocationURI) {
return ''; // do not ask for input in tests (https://github.com/microsoft/vscode/issues/112264)
}
return new Promise<string>(resolve => {
// Inform user about permissions problem (https://github.com/microsoft/vscode/issues/112089)
const listener = new DisposableStore();
const handle = this.notificationService.prompt(
Severity.Error,
localize('clipboardError', "Unable to read from the browser's clipboard. Please make sure you have granted access for this website to read from the clipboard."),
[{
label: localize('retry', "Retry"),
run: async () => {
listener.dispose();
resolve(await this.readText(type));
}
}, {
label: localize('learnMore', "Learn More"),
run: () => this.openerService.open('https://go.microsoft.com/fwlink/?linkid=2151362')
}],
{
sticky: true
}
);
// Always resolve the promise once the notification closes
listener.add(once(handle.onDidClose)(() => resolve('')));
});
}
}
}
registerSingleton(IClipboardService, BrowserClipboardService, true);

View File

@@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import { Event, Emitter } from 'vs/base/common/event';
import * as errors from 'vs/base/common/errors';
import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { ConfigurationModel, ConfigurationModelParser, UserSettings } from 'vs/platform/configuration/common/configurationModels';
@@ -22,6 +21,7 @@ import { equals } from 'vs/base/common/objects';
import { IConfigurationModel } from 'vs/platform/configuration/common/configuration';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { hash } from 'vs/base/common/hash';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
export class UserConfiguration extends Disposable {
@@ -36,10 +36,11 @@ export class UserConfiguration extends Disposable {
constructor(
private readonly userSettingsResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService
private readonly fileService: IFileService,
private readonly uriIdentityService: IUriIdentityService,
) {
super();
this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, this.fileService);
this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, uriIdentityService.extUri, this.fileService);
this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
}
@@ -53,9 +54,9 @@ export class UserConfiguration extends Disposable {
return this.userConfiguration.value!.loadConfiguration();
}
const folder = resources.dirname(this.userSettingsResource);
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(folder, `${name}.json`)]));
const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService);
const folder = this.uriIdentityService.extUri.dirname(this.userSettingsResource);
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, this.uriIdentityService.extUri.joinPath(folder, `${name}.json`)]));
const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService);
const configurationModel = await fileServiceBasedConfiguration.loadConfiguration();
this.userConfiguration.value = fileServiceBasedConfiguration;
@@ -88,11 +89,12 @@ class FileServiceBasedConfiguration extends Disposable {
private readonly settingsResources: URI[],
private readonly standAloneConfigurationResources: [string, URI][],
private readonly scopes: ConfigurationScope[] | undefined,
private fileService: IFileService
private fileService: IFileService,
private uriIdentityService: IUriIdentityService
) {
super();
this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)];
this._register(combinedDisposable(...this.allResources.map(resource => this.fileService.watch(resources.dirname(resource)))));
this._register(combinedDisposable(...this.allResources.map(resource => this.fileService.watch(uriIdentityService.extUri.dirname(resource)))));
this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes);
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
@@ -165,7 +167,7 @@ class FileServiceBasedConfiguration extends Disposable {
return true;
}
// One of the resource's parent got deleted
if (this.allResources.some(resource => event.contains(resources.dirname(resource), FileChangeType.DELETED))) {
if (this.allResources.some(resource => event.contains(this.uriIdentityService.extUri.dirname(resource), FileChangeType.DELETED))) {
return true;
}
return false;
@@ -187,10 +189,14 @@ export class RemoteUserConfiguration extends Disposable {
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
private readonly _onDidInitialize = this._register(new Emitter<ConfigurationModel>());
public readonly onDidInitialize = this._onDidInitialize.event;
constructor(
remoteAuthority: string,
configurationCache: IConfigurationCache,
fileService: IFileService,
uriIdentityService: IUriIdentityService,
remoteAgentService: IRemoteAgentService
) {
super();
@@ -198,13 +204,14 @@ export class RemoteUserConfiguration extends Disposable {
this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache);
remoteAgentService.getEnvironment().then(async environment => {
if (environment) {
const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._fileService));
const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._fileService, uriIdentityService));
this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
this._userConfigurationInitializationPromise = userConfiguration.initialize();
const configurationModel = await this._userConfigurationInitializationPromise;
this._userConfiguration.dispose();
this._userConfiguration = userConfiguration;
this.onDidUserConfigurationChange(configurationModel);
this._onDidInitialize.fire(configurationModel);
}
});
}
@@ -256,7 +263,8 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable {
constructor(
private readonly configurationResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService
private readonly fileService: IFileService,
private readonly uriIdentityService: IUriIdentityService,
) {
super();
@@ -279,7 +287,7 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable {
}
private watchDirectory(): void {
const directory = resources.dirname(this.configurationResource);
const directory = this.uriIdentityService.extUri.dirname(this.configurationResource);
this.directoryWatcherDisposable = this.fileService.watch(directory);
}
@@ -389,8 +397,8 @@ export class WorkspaceConfiguration extends Disposable {
private readonly _fileService: IFileService;
private readonly _cachedConfiguration: CachedWorkspaceConfiguration;
private _workspaceConfiguration: IWorkspaceConfiguration;
private _workspaceConfigurationChangeDisposable: IDisposable = Disposable.None;
private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration;
private _workspaceConfigurationDisposables = this._register(new DisposableStore());
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
private readonly _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
@@ -458,10 +466,9 @@ export class WorkspaceConfiguration extends Disposable {
}
private doInitialize(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void {
this._workspaceConfiguration.dispose();
this._workspaceConfigurationChangeDisposable.dispose();
this._workspaceConfiguration = this._register(fileServiceBasedWorkspaceConfiguration);
this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true)));
this._workspaceConfigurationDisposables.clear();
this._workspaceConfiguration = this._workspaceConfigurationDisposables.add(fileServiceBasedWorkspaceConfiguration);
this._workspaceConfigurationDisposables.add(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true)));
this._initialized = true;
}
@@ -482,19 +489,7 @@ export class WorkspaceConfiguration extends Disposable {
}
}
interface IWorkspaceConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
workspaceSettings: ConfigurationModel;
workspaceIdentifier: IWorkspaceIdentifier | null;
load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void>;
getConfigurationModel(): ConfigurationModel;
getFolders(): IStoredWorkspaceFolder[];
getWorkspaceSettings(): ConfigurationModel;
reprocessWorkspaceSettings(): ConfigurationModel;
}
class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
class FileServiceBasedWorkspaceConfiguration extends Disposable {
workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
workspaceSettings: ConfigurationModel;
@@ -578,16 +573,14 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork
}
}
class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration {
class CachedWorkspaceConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
readonly onDidChange: Event<void> = Event.None;
workspaceConfigurationModelParser: WorkspaceConfigurationModelParser;
workspaceSettings: ConfigurationModel;
constructor(private readonly configurationCache: IConfigurationCache) {
super();
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser('');
this.workspaceSettings = new ConfigurationModel();
}
@@ -643,16 +636,9 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi
}
}
export interface IFolderConfiguration extends IDisposable {
readonly onDidChange: Event<void>;
loadConfiguration(): Promise<ConfigurationModel>;
reprocess(): ConfigurationModel;
}
class CachedFolderConfiguration {
class CachedFolderConfiguration extends Disposable implements IFolderConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
readonly onDidChange = Event.None;
private configurationModel: ConfigurationModel;
private readonly key: ConfigurationKey;
@@ -662,7 +648,6 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati
configFolderRelativePath: string,
private readonly configurationCache: IConfigurationCache
) {
super();
this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) };
this.configurationModel = new ConfigurationModel();
}
@@ -694,13 +679,12 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati
}
}
export class FolderConfiguration extends Disposable implements IFolderConfiguration {
export class FolderConfiguration extends Disposable {
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private folderConfiguration: IFolderConfiguration;
private folderConfigurationDisposable: IDisposable = Disposable.None;
private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration;
private readonly configurationFolder: URI;
private cachedFolderConfiguration: CachedFolderConfiguration;
@@ -709,25 +693,24 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
configFolderRelativePath: string,
private readonly workbenchState: WorkbenchState,
fileService: IFileService,
uriIdentityService: IUriIdentityService,
private readonly configurationCache: IConfigurationCache
) {
super();
this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath);
this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath);
this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache);
if (this.configurationCache.needsCaching(workspaceFolder.uri)) {
this.folderConfiguration = this.cachedFolderConfiguration;
whenProviderRegistered(workspaceFolder.uri, fileService)
.then(() => {
this.folderConfiguration.dispose();
this.folderConfigurationDisposable.dispose();
this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService);
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService));
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
this.onDidFolderConfigurationChange();
});
} else {
this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService);
this.folderConfigurationDisposable = this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService));
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
}
}
@@ -744,10 +727,10 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
this._onDidChange.fire();
}
private createFileServiceBasedConfiguration(fileService: IFileService) {
const settingsResources = [resources.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)];
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(this.configurationFolder, `${name}.json`)]));
return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService);
private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService) {
const settingsResources = [uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)];
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)]));
return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService, uriIdentityService);
}
private updateCache(): Promise<void> {

View File

@@ -10,20 +10,19 @@ import { equals } from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue, Barrier, runWhenIdle } from 'vs/base/common/async';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { isEqual, dirname } from 'vs/base/common/resources';
import { mark } from 'vs/base/common/performance';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IFileService } from 'vs/platform/files/common/files';
@@ -38,11 +37,12 @@ class Workspace extends BaseWorkspace {
initialized: boolean = false;
}
export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService {
export class WorkspaceService extends Disposable implements IWorkbenchConfigurationService, IWorkspaceContextService {
public _serviceBrand: undefined;
private workspace!: Workspace;
private initRemoteUserConfigurationBarrier: Barrier;
private completeWorkspaceBarrier: Barrier;
private readonly configurationCache: IConfigurationCache;
private _configuration: Configuration;
@@ -70,6 +70,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
protected readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;
private readonly configurationRegistry: IConfigurationRegistry;
// TODO@sandeep debt with cyclic dependencies
private configurationEditingService!: ConfigurationEditingService;
private jsonEditingService!: JSONEditingService;
@@ -86,13 +88,14 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
) {
super();
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
this.configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
// register defaults before creating default configuration model
// so that the model is not required to be updated after registering
if (environmentService.options?.configurationDefaults) {
configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]);
this.configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]);
}
this.initRemoteUserConfigurationBarrier = new Barrier();
this.completeWorkspaceBarrier = new Barrier();
this.defaultConfiguration = new DefaultConfigurationModel();
this.configurationCache = configurationCache;
@@ -101,12 +104,19 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
this.logService = logService;
this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService));
this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService));
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
if (remoteAuthority) {
this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, fileService, remoteAgentService));
this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration)));
const remoteUserConfiguration = this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, fileService, uriIdentityService, remoteAgentService));
this._register(remoteUserConfiguration.onDidInitialize(remoteUserConfigurationModel => {
this._register(remoteUserConfiguration.onDidChangeConfiguration(remoteUserConfigurationModel => this.onRemoteUserConfigurationChanged(remoteUserConfigurationModel)));
this.onRemoteUserConfigurationChanged(remoteUserConfigurationModel);
this.initRemoteUserConfigurationBarrier.open();
}));
} else {
this.initRemoteUserConfigurationBarrier.open();
}
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, fileService));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => {
this.onWorkspaceConfigurationChanged().then(() => {
@@ -115,7 +125,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
});
}));
this._register(configurationRegistry.onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this._register(this.configurationRegistry.onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this.workspaceEditingQueue = new Queue<void>();
}
@@ -167,12 +177,19 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return !!this.getWorkspaceFolder(resource);
}
public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
public isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean {
switch (this.getWorkbenchState()) {
case WorkbenchState.FOLDER:
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && isEqual(workspaceIdentifier, this.workspace.folders[0].uri);
let folderUri: URI | undefined = undefined;
if (URI.isUri(workspaceIdOrFolder)) {
folderUri = workspaceIdOrFolder;
} else if (isSingleFolderWorkspaceIdentifier(workspaceIdOrFolder)) {
folderUri = workspaceIdOrFolder.uri;
}
return URI.isUri(folderUri) && this.uriIdentityService.extUri.isEqual(folderUri, this.workspace.folders[0].uri);
case WorkbenchState.WORKSPACE:
return isWorkspaceIdentifier(workspaceIdentifier) && this.workspace.id === workspaceIdentifier.id;
return isWorkspaceIdentifier(workspaceIdOrFolder) && this.workspace.id === workspaceIdOrFolder.id;
}
return false;
}
@@ -207,8 +224,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
// Recompute current workspace folders if we have folders to add
const workspaceConfigPath = this.getWorkspace().configuration!;
const workspaceConfigFolder = dirname(workspaceConfigPath);
currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigPath);
const workspaceConfigFolder = this.uriIdentityService.extUri.dirname(workspaceConfigPath);
currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigPath, this.uriIdentityService.extUri);
const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
@@ -224,7 +241,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
continue;
}
} catch (e) { /* Ignore */ }
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, slashForPath));
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, slashForPath, this.uriIdentityService.extUri));
}
// Apply to array of newStoredFolders
@@ -256,7 +273,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
private contains(resources: URI[], toCheck: URI): boolean {
return resources.some(resource => isEqual(resource, toCheck));
return resources.some(resource => this.uriIdentityService.extUri.isEqual(resource, toCheck));
}
// Workspace Configuration Service Impl
@@ -280,13 +297,23 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
updateValue(key: string, value: any, target: ConfigurationTarget): Promise<void>;
updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): Promise<void>;
updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): Promise<void>;
updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise<void> {
return this.cyclicDependency.then(() => {
const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined;
const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3);
return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
: Promise.resolve();
});
async updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise<void> {
await this.cyclicDependency;
const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined;
const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3;
const targets: ConfigurationTarget[] = target ? [target] : [];
if (!targets.length) {
const inspect = this.inspect(key, overrides);
targets.push(...this.deriveConfigurationTargets(key, value, inspect));
// Remove the setting, if the value is same as default value and is updated only in user target
if (equals(value, inspect.defaultValue) && targets.length === 1 && (targets[0] === ConfigurationTarget.USER || targets[0] === ConfigurationTarget.USER_LOCAL)) {
value = undefined;
}
}
await Promise.all(targets.map(target => this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)));
}
async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise<void> {
@@ -297,7 +324,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return;
}
if (IWorkspaceFolder.isIWorkspaceFolder(target)) {
if (isWorkspaceFolder(target)) {
await this.reloadWorkspaceFolderConfiguration(target);
return;
}
@@ -336,14 +363,18 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this._configuration.keys();
}
public async whenRemoteConfigurationLoaded(): Promise<void> {
await this.initRemoteUserConfigurationBarrier.wait();
}
async initialize(arg: IWorkspaceInitializationPayload): Promise<void> {
mark('willInitWorkspaceService');
mark('code/willInitWorkspaceService');
const workspace = await this.createWorkspace(arg);
await this.updateWorkspaceAndInitializeConfiguration(workspace);
this.checkAndMarkWorkspaceComplete();
mark('didInitWorkspaceService');
mark('code/didInitWorkspaceService');
}
acquireInstantiationService(instantiationService: IInstantiationService): void {
@@ -362,7 +393,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this.createMultiFolderWorkspace(arg);
}
if (isSingleFolderWorkspaceInitializationPayload(arg)) {
if (isSingleFolderWorkspaceIdentifier(arg)) {
return this.createSingleFolderWorkspace(arg);
}
@@ -373,7 +404,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath })
.then(() => {
const workspaceConfigPath = workspaceIdentifier.configPath;
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath);
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri);
const workspaceId = workspaceIdentifier.id;
const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = this.workspaceConfiguration.initialized;
@@ -381,14 +412,14 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
});
}
private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(singleFolder.id, [toWorkspaceFolder(singleFolder.folder)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Promise<Workspace> {
const workspace = new Workspace(singleFolderWorkspaceIdentifier.id, [toWorkspaceFolder(singleFolderWorkspaceIdentifier.uri)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = true;
return Promise.resolve(workspace);
}
private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(emptyWorkspace.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
private createEmptyWorkspace(emptyWorkspacePayload: IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
const workspace = new Workspace(emptyWorkspacePayload.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = true;
return Promise.resolve(workspace);
}
@@ -438,7 +469,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
if (!this.localUserConfiguration.hasTasksLoaded) {
// Reload local user configuration again to load user tasks
runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000);
this._register(runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000));
}
});
}
@@ -587,7 +618,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private async onWorkspaceConfigurationChanged(): Promise<void> {
if (this.workspace && this.workspace.configuration) {
let newFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration);
let newFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration, this.uriIdentityService.extUri);
// Validate only if workspace is initialized
if (this.workspace.initialized) {
@@ -662,7 +693,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return Promise.all([...folders.map(folder => {
let folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
if (!folderConfiguration) {
folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.configurationCache);
folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.configurationCache);
this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder)));
this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
}
@@ -736,31 +767,31 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
});
}
private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides | undefined, target: ConfigurationTarget): ConfigurationTarget | undefined {
if (target) {
return target;
private deriveConfigurationTargets(key: string, value: any, inspect: IConfigurationValue<any>): ConfigurationTarget[] {
if (equals(value, inspect.value)) {
return [];
}
const definedTargets: ConfigurationTarget[] = [];
if (inspect.workspaceFolderValue !== undefined) {
definedTargets.push(ConfigurationTarget.WORKSPACE_FOLDER);
}
if (inspect.workspaceValue !== undefined) {
definedTargets.push(ConfigurationTarget.WORKSPACE);
}
if (inspect.userRemoteValue !== undefined) {
definedTargets.push(ConfigurationTarget.USER_REMOTE);
}
if (inspect.userLocalValue !== undefined) {
definedTargets.push(ConfigurationTarget.USER_LOCAL);
}
if (value === undefined) {
// Ignore. But expected is to remove the value from all targets
return undefined;
// Remove the setting in all defined targets
return definedTargets;
}
const inspect = this.inspect(key, overrides);
if (equals(value, inspect.value)) {
// No change. So ignore.
return undefined;
}
if (inspect.workspaceFolderValue !== undefined) {
return ConfigurationTarget.WORKSPACE_FOLDER;
}
if (inspect.workspaceValue !== undefined) {
return ConfigurationTarget.WORKSPACE;
}
return ConfigurationTarget.USER;
return [definedTargets[0] || ConfigurationTarget.USER];
}
private triggerConfigurationChange(change: IConfigurationChange, previous: { data: IConfigurationData, workspace?: Workspace } | undefined, target: ConfigurationTarget): void {
@@ -786,8 +817,14 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private toEditableConfigurationTarget(target: ConfigurationTarget, key: string): EditableConfigurationTarget | null {
if (target === ConfigurationTarget.USER) {
if (this.inspect(key).userRemoteValue !== undefined) {
return EditableConfigurationTarget.USER_REMOTE;
if (this.remoteUserConfiguration) {
const scope = this.configurationRegistry.getConfigurationProperties()[key]?.scope;
if (scope === ConfigurationScope.MACHINE || scope === ConfigurationScope.MACHINE_OVERRIDABLE) {
return EditableConfigurationTarget.USER_REMOTE;
}
if (this.inspect(key).userRemoteValue !== undefined) {
return EditableConfigurationTarget.USER_REMOTE;
}
}
return EditableConfigurationTarget.USER_LOCAL;
}

View File

@@ -5,6 +5,8 @@
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const FOLDER_CONFIG_FOLDER_NAME = '.vscode';
export const FOLDER_SETTINGS_NAME = 'settings';
@@ -43,4 +45,13 @@ export interface IConfigurationCache {
}
export const IWorkbenchConfigurationService = createDecorator<IWorkbenchConfigurationService>('configurationService');
export interface IWorkbenchConfigurationService extends IConfigurationService {
/**
* A promise that resolves when the remote configuration is loaded in a remote window.
* The promise is resolved immediately if the window is not remote.
*/
whenRemoteConfigurationLoaded(): Promise<void>;
}
export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}';

View File

@@ -5,7 +5,6 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import * as json from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Queue } from 'vs/base/common/async';
@@ -29,6 +28,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
export const enum ConfigurationEditingErrorCode {
@@ -155,7 +155,8 @@ export class ConfigurationEditingService {
@INotificationService private readonly notificationService: INotificationService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@IEditorService private readonly editorService: IEditorService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
this.queue = new Queue<void>();
remoteAgentService.getEnvironment().then(environment => {
@@ -437,8 +438,8 @@ export class ConfigurationEditingService {
}
private defaultResourceValue(resource: URI): string {
const basename: string = resources.basename(resource);
const configurationValue: string = basename.substr(0, basename.length - resources.extname(resource).length);
const basename: string = this.uriIdentityService.extUri.basename(resource);
const configurationValue: string = basename.substr(0, basename.length - this.uriIdentityService.extUri.extname(resource).length);
switch (configurationValue) {
case TASKS_CONFIGURATION_KEY: return TASKS_DEFAULT;
default: return '{}';
@@ -585,7 +586,7 @@ export class ConfigurationEditingService {
private getConfigurationFileResource(target: EditableConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null {
if (target === EditableConfigurationTarget.USER_LOCAL) {
if (relativePath) {
return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath);
return this.uriIdentityService.extUri.joinPath(this.uriIdentityService.extUri.dirname(this.environmentService.settingsResource), relativePath);
} else {
return this.environmentService.settingsResource;
}

View File

@@ -3,18 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pfs from 'vs/base/node/pfs';
import { join } from 'vs/base/common/path';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { joinPath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
export class ConfigurationCache implements IConfigurationCache {
private readonly cachedConfigurations: Map<string, CachedConfiguration> = new Map<string, CachedConfiguration>();
constructor(private readonly environmentService: INativeWorkbenchEnvironmentService) {
constructor(private readonly cacheHome: URI, private readonly fileService: IFileService) {
}
needsCaching(resource: URI): boolean {
@@ -38,32 +38,31 @@ export class ConfigurationCache implements IConfigurationCache {
const k = `${type}:${key}`;
let cachedConfiguration = this.cachedConfigurations.get(k);
if (!cachedConfiguration) {
cachedConfiguration = new CachedConfiguration({ type, key }, this.environmentService);
cachedConfiguration = new CachedConfiguration({ type, key }, this.cacheHome, this.fileService);
this.cachedConfigurations.set(k, cachedConfiguration);
}
return cachedConfiguration;
}
}
class CachedConfiguration {
private cachedConfigurationFolderPath: string;
private cachedConfigurationFilePath: string;
private cachedConfigurationFolderResource: URI;
private cachedConfigurationFileResource: URI;
constructor(
{ type, key }: ConfigurationKey,
environmentService: INativeWorkbenchEnvironmentService
cacheHome: URI,
private readonly fileService: IFileService
) {
this.cachedConfigurationFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', type, key);
this.cachedConfigurationFilePath = join(this.cachedConfigurationFolderPath, type === 'workspaces' ? 'workspace.json' : 'configuration.json');
this.cachedConfigurationFolderResource = joinPath(cacheHome, 'CachedConfigurations', type, key);
this.cachedConfigurationFileResource = joinPath(this.cachedConfigurationFolderResource, type === 'workspaces' ? 'workspace.json' : 'configuration.json');
}
async read(): Promise<string> {
try {
const content = await pfs.readFile(this.cachedConfigurationFilePath);
return content.toString();
const content = await this.fileService.readFile(this.cachedConfigurationFileResource);
return content.value.toString();
} catch (e) {
return '';
}
@@ -72,18 +71,30 @@ class CachedConfiguration {
async save(content: string): Promise<void> {
const created = await this.createCachedFolder();
if (created) {
await pfs.writeFile(this.cachedConfigurationFilePath, content);
await this.fileService.writeFile(this.cachedConfigurationFileResource, VSBuffer.fromString(content));
}
}
remove(): Promise<void> {
return pfs.rimraf(this.cachedConfigurationFolderPath);
async remove(): Promise<void> {
try {
await this.fileService.del(this.cachedConfigurationFolderResource, { recursive: true, useTrash: false });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
throw error;
}
}
}
private createCachedFolder(): Promise<boolean> {
return Promise.resolve(pfs.exists(this.cachedConfigurationFolderPath))
.then(undefined, () => false)
.then(exists => exists ? exists : pfs.mkdirp(this.cachedConfigurationFolderPath).then(() => true, () => false));
private async createCachedFolder(): Promise<boolean> {
if (await this.fileService.exists(this.cachedConfigurationFolderResource)) {
return true;
}
try {
await this.fileService.createFolder(this.cachedConfigurationFolderResource);
return true;
} catch (error) {
return false;
}
}
}

View File

@@ -0,0 +1,395 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sinon from 'sinon';
import * as assert from 'assert';
import * as json from 'vs/base/common/json';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestEnvironmentService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ConfigurationEditingService, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CommandService } from 'vs/workbench/services/commands/common/commandService';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { joinPath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl';
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
suite('ConfigurationEditingService', () => {
let instantiationService: TestInstantiationService;
let environmentService: BrowserWorkbenchEnvironmentService;
let fileService: IFileService;
let workspaceService: WorkspaceService;
let testObject: ConfigurationEditingService;
const disposables = new DisposableStore();
suiteSetup(() => {
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationEditing.service.testSetting': {
'type': 'string',
'default': 'isSet'
},
'configurationEditing.service.testSettingTwo': {
'type': 'string',
'default': 'isSet'
},
'configurationEditing.service.testSettingThree': {
'type': 'string',
'default': 'isSet'
}
}
});
});
setup(async () => {
const logService = new NullLogService();
fileService = disposables.add(new FileService(logService));
const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));
const workspaceFolder = joinPath(ROOT, uuid.generateUuid());
await fileService.createFolder(workspaceFolder);
instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
environmentService = TestEnvironmentService;
instantiationService.stub(IEnvironmentService, environmentService);
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
disposables.add(fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, logService))));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService()));
instantiationService.stub(IWorkspaceContextService, workspaceService);
await workspaceService.initialize(getSingleFolderWorkspaceIdentifier(workspaceFolder));
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IKeybindingEditingService, disposables.add(instantiationService.createInstance(KeybindingsEditingService)));
instantiationService.stub(ITextFileService, disposables.add(instantiationService.createInstance(TestTextFileService)));
instantiationService.stub(ITextModelService, <ITextModelService>disposables.add(instantiationService.createInstance(TextModelResolverService)));
instantiationService.stub(ICommandService, CommandService);
testObject = instantiationService.createInstance(ConfigurationEditingService);
});
teardown(() => disposables.clear());
test('errors cases - invalid key', async () => {
try {
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'unknown.key', value: 'value' });
assert.fail('Should fail with ERROR_UNKNOWN_KEY');
} catch (error) {
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY);
}
});
test('errors cases - no workspace', async () => {
await workspaceService.initialize({ id: uuid.generateUuid() });
try {
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' });
assert.fail('Should fail with ERROR_NO_WORKSPACE_OPENED');
} catch (error) {
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED);
}
});
test('errors cases - invalid configuration', async () => {
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(',,,,,,,,,,,,,,'));
try {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' });
assert.fail('Should fail with ERROR_INVALID_CONFIGURATION');
} catch (error) {
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION);
}
});
test('errors cases - invalid global tasks configuration', async () => {
const resource = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(resource, VSBuffer.fromString(',,,,,,,,,,,,,,'));
try {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.configurationEditing.service.testSetting', value: 'value' });
assert.fail('Should fail with ERROR_INVALID_CONFIGURATION');
} catch (error) {
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION);
}
});
test('errors cases - dirty', async () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
try {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' });
assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.');
} catch (error) {
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY);
}
});
test('dirty error is not thrown if not asked to save', async () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotSave: true });
});
test('do not notify error', async () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
const target = sinon.stub();
instantiationService.stub(INotificationService, <INotificationService>{ prompt: target, _serviceBrand: undefined, notify: null!, error: null!, info: null!, warn: null!, status: null!, setFilter: null! });
try {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true });
assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.');
} catch (error) {
assert.equal(false, target.calledOnce);
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY);
}
});
test('write one setting - empty file', async () => {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' });
const contents = await fileService.readFile(environmentService.settingsResource);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['configurationEditing.service.testSetting'], 'value');
});
test('write one setting - existing file', async () => {
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' });
const contents = await fileService.readFile(environmentService.settingsResource);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['configurationEditing.service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
test('remove an existing setting - existing file', async () => {
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "my.super.setting": "my.super.value", "configurationEditing.service.testSetting": "value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: undefined });
const contents = await fileService.readFile(environmentService.settingsResource);
const parsed = json.parse(contents.value.toString());
assert.deepEqual(Object.keys(parsed), ['my.super.setting']);
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
test('remove non existing setting - existing file', async () => {
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: undefined });
const contents = await fileService.readFile(environmentService.settingsResource);
const parsed = json.parse(contents.value.toString());
assert.deepEqual(Object.keys(parsed), ['my.super.setting']);
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
test('write overridable settings to user settings', async () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value });
const contents = await fileService.readFile(environmentService.settingsResource);
const parsed = json.parse(contents.value.toString());
assert.deepEqual(parsed[key], value);
});
test('write overridable settings to workspace settings', async () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key, value });
const contents = await fileService.readFile(joinPath(workspaceService.getWorkspace().folders[0].uri, FOLDER_SETTINGS_PATH));
const parsed = json.parse(contents.value.toString());
assert.deepEqual(parsed[key], value);
});
test('write overridable settings to workspace folder settings', async () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
const folderSettingsFile = joinPath(workspaceService.getWorkspace().folders[0].uri, FOLDER_SETTINGS_PATH);
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE_FOLDER, { key, value }, { scopes: { resource: folderSettingsFile } });
const contents = await fileService.readFile(folderSettingsFile);
const parsed = json.parse(contents.value.toString());
assert.deepEqual(parsed[key], value);
});
test('write workspace standalone setting - empty file', async () => {
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['service.testSetting'], 'value');
});
test('write user standalone setting - empty file', async () => {
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.service.testSetting', value: 'value' });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['service.testSetting'], 'value');
});
test('write workspace standalone setting - existing file', async () => {
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
test('write user standalone setting - existing file', async () => {
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.service.testSetting', value: 'value' });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
test('write workspace standalone setting - empty file - full JSON', async () => {
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write user standalone setting - empty file - full JSON', async () => {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write workspace standalone setting - existing file - full JSON', async () => {
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write user standalone setting - existing file - full JSON', async () => {
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": "my.super.value" }'));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write workspace standalone setting - existing file with JSON errors - full JSON', async () => {
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": ')); // invalid JSON
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write user standalone setting - existing file with JSON errors - full JSON', async () => {
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString('{ "my.super.setting": ')); // invalid JSON
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } });
const contents = await fileService.readFile(target);
const parsed = json.parse(contents.value.toString());
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
test('write workspace standalone setting should replace complete file', async () => {
const target = joinPath(workspaceService.getWorkspace().folders[0].uri, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString(`{
"version": "1.0.0",
"tasks": [
{
"taskName": "myTask1"
},
{
"taskName": "myTask2"
}
]
}`));
await testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } });
const actual = await fileService.readFile(target);
const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t');
assert.equal(actual.value.toString(), expected);
});
test('write user standalone setting should replace complete file', async () => {
const target = joinPath(environmentService.userRoamingDataHome, USER_STANDALONE_CONFIGURATIONS['tasks']);
await fileService.writeFile(target, VSBuffer.fromString(`{
"version": "1.0.0",
"tasks": [
{
"taskName": "myTask1"
},
{
"taskName": "myTask2"
}
]
}`));
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } });
const actual = await fileService.readFile(target);
const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t');
assert.equal(actual.value.toString(), expected);
});
});

View File

@@ -1,394 +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 * as sinon from 'sinon';
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as fs from 'fs';
import * as json from 'vs/base/common/json';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestProductService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CommandService } from 'vs/workbench/services/commands/common/commandService';
import { URI } from 'vs/base/common/uri';
import { createHash } from 'crypto';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache';
import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { DisposableStore } from 'vs/base/common/lifecycle';
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(private _appSettingsHome: URI) {
super(TestWorkbenchConfiguration, TestProductService);
}
get appSettingsHome() { return this._appSettingsHome; }
}
suite('ConfigurationEditingService', () => {
let instantiationService: TestInstantiationService;
let testObject: ConfigurationEditingService;
let parentDir: string;
let workspaceDir: string;
let globalSettingsFile: string;
let globalTasksFile: string;
let workspaceSettingsDir;
const disposables = new DisposableStore();
suiteSetup(() => {
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationEditing.service.testSetting': {
'type': 'string',
'default': 'isSet'
},
'configurationEditing.service.testSettingTwo': {
'type': 'string',
'default': 'isSet'
},
'configurationEditing.service.testSettingThree': {
'type': 'string',
'default': 'isSet'
}
}
});
});
setup(async () => {
await setUpWorkspace();
await setUpServices();
});
async function setUpWorkspace(): Promise<void> {
const id = uuid.generateUuid();
parentDir = path.join(os.tmpdir(), 'vsctests', id);
workspaceDir = path.join(parentDir, 'workspaceconfig', id);
globalSettingsFile = path.join(workspaceDir, 'settings.json');
globalTasksFile = path.join(workspaceDir, 'tasks.json');
workspaceSettingsDir = path.join(workspaceDir, '.vscode');
return await mkdirp(workspaceSettingsDir, 493);
}
async function setUpServices(noWorkspace: boolean = false): Promise<void> {
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestWorkbenchEnvironmentService(URI.file(workspaceDir));
instantiationService.stub(IEnvironmentService, environmentService);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
const fileService = disposables.add(new FileService(new NullLogService()));
const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(new NullLogService()));
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService())));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService()));
instantiationService.stub(IWorkspaceContextService, workspaceService);
await workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') });
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
instantiationService.stub(ICommandService, CommandService);
testObject = instantiationService.createInstance(ConfigurationEditingService);
}
teardown(() => {
disposables.clear();
if (workspaceDir) {
return rimraf(workspaceDir, RimRafMode.MOVE);
}
return undefined;
});
test('errors cases - invalid key', () => {
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'unknown.key', value: 'value' })
.then(() => assert.fail('Should fail with ERROR_UNKNOWN_KEY'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY));
});
test('errors cases - no workspace', () => {
return setUpServices(true)
.then(() => testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' }))
.then(() => assert.fail('Should fail with ERROR_NO_WORKSPACE_OPENED'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED));
});
function errorCasesInvalidConfig(file: string, key: string) {
fs.writeFileSync(file, ',,,,,,,,,,,,,,');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value: 'value' })
.then(() => assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION));
}
test('errors cases - invalid configuration', () => {
return errorCasesInvalidConfig(globalSettingsFile, 'configurationEditing.service.testSetting');
});
test('errors cases - invalid global tasks configuration', () => {
return errorCasesInvalidConfig(globalTasksFile, 'tasks.configurationEditing.service.testSetting');
});
test('errors cases - dirty', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' })
.then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY));
});
test('dirty error is not thrown if not asked to save', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotSave: true })
.then(() => null, error => assert.fail('Should not fail.'));
});
test('do not notify error', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
const target = sinon.stub();
instantiationService.stub(INotificationService, <INotificationService>{ prompt: target, _serviceBrand: undefined, notify: null!, error: null!, info: null!, warn: null!, status: null!, setFilter: null! });
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true })
.then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'),
(error: ConfigurationEditingError) => {
assert.equal(false, target.calledOnce);
assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY);
});
});
test('write one setting - empty file', () => {
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' })
.then(() => {
const contents = fs.readFileSync(globalSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['configurationEditing.service.testSetting'], 'value');
});
});
test('write one setting - existing file', () => {
fs.writeFileSync(globalSettingsFile, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' })
.then(() => {
const contents = fs.readFileSync(globalSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['configurationEditing.service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
});
test('remove an existing setting - existing file', () => {
fs.writeFileSync(globalSettingsFile, '{ "my.super.setting": "my.super.value", "configurationEditing.service.testSetting": "value" }');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: undefined })
.then(() => {
const contents = fs.readFileSync(globalSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.deepEqual(Object.keys(parsed), ['my.super.setting']);
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
});
test('remove non existing setting - existing file', () => {
fs.writeFileSync(globalSettingsFile, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: undefined })
.then(() => {
const contents = fs.readFileSync(globalSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.deepEqual(Object.keys(parsed), ['my.super.setting']);
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
});
test('write overridable settings to user settings', () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value })
.then(() => {
const contents = fs.readFileSync(globalSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.deepEqual(parsed[key], value);
});
});
test('write overridable settings to workspace settings', () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key, value })
.then(() => {
const target = path.join(workspaceDir, FOLDER_SETTINGS_PATH);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.deepEqual(parsed[key], value);
});
});
test('write overridable settings to workspace folder settings', () => {
const key = '[language]';
const value = { 'configurationEditing.service.testSetting': 'overridden value' };
const folderSettingsFile = path.join(workspaceDir, FOLDER_SETTINGS_PATH);
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE_FOLDER, { key, value }, { scopes: { resource: URI.file(folderSettingsFile) } })
.then(() => {
const contents = fs.readFileSync(folderSettingsFile).toString('utf8');
const parsed = json.parse(contents);
assert.deepEqual(parsed[key], value);
});
});
function writeStandaloneSettingEmptyFile(configTarget: EditableConfigurationTarget, pathMap: any) {
return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' })
.then(() => {
const target = path.join(workspaceDir, pathMap['tasks']);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['service.testSetting'], 'value');
});
}
test('write workspace standalone setting - empty file', () => {
return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - empty file', () => {
return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExitingFile(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
}
test('write workspace standalone setting - existing file', () => {
return writeStandaloneSettingExitingFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - existing file', () => {
return writeStandaloneSettingExitingFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingEmptyFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const target = path.join(workspaceDir, pathMap['tasks']);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
}
test('write workspace standalone setting - empty file - full JSON', () => {
return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - empty file - full JSON', () => {
return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExistingFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
}
test('write workspace standalone setting - existing file - full JSON', () => {
return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - existing file - full JSON', () => {
return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExistingFileWithJsonErrorFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
}
test('write workspace standalone setting - existing file with JSON errors - full JSON', () => {
return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - existing file with JSON errors - full JSON', () => {
return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingShouldReplace(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, `{
"version": "1.0.0",
"tasks": [
{
"taskName": "myTask1"
},
{
"taskName": "myTask2"
}
]
}`);
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } })
.then(() => {
const actual = fs.readFileSync(target).toString('utf8');
const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t');
assert.equal(actual, expected);
});
}
test('write workspace standalone setting should replace complete file', () => {
return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting should replace complete file', () => {
return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
});

View File

@@ -23,7 +23,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { stripCodicons } from 'vs/base/common/codicons';
import { stripIcons } from 'vs/base/common/iconLabels';
import { coalesce } from 'vs/base/common/arrays';
export class ContextMenuService extends Disposable implements IContextMenuService {
@@ -118,7 +118,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
}
}
private createMenu(delegate: IContextMenuDelegate, entries: IAction[], onHide: () => void, submenuIds = new Set<string>()): IContextMenuItem[] {
private createMenu(delegate: IContextMenuDelegate, entries: readonly IAction[], onHide: () => void, submenuIds = new Set<string>()): IContextMenuItem[] {
const actionRunner = delegate.actionRunner || new ActionRunner();
return coalesce(entries.map(entry => this.createMenuItem(delegate, entry, actionRunner, onHide, submenuIds)));
}
@@ -137,7 +137,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
}
return {
label: unmnemonicLabel(stripCodicons(entry.label)).trim(),
label: unmnemonicLabel(stripIcons(entry.label)).trim(),
submenu: this.createMenu(delegate, entry.actions, onHide, new Set([...submenuIds, entry.id]))
};
}
@@ -154,7 +154,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
}
const item: IContextMenuItem = {
label: unmnemonicLabel(stripCodicons(entry.label)).trim(),
label: unmnemonicLabel(stripIcons(entry.label)).trim(),
checked: !!entry.checked,
type,
enabled: !!entry.enabled,

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICredentialsService, ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials';
import { ICredentialsService, ICredentialsProvider, ICredentialsChangeEvent } from 'vs/workbench/services/credentials/common/credentials';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Emitter } from 'vs/base/common/event';
@@ -13,7 +13,7 @@ export class BrowserCredentialsService extends Disposable implements ICredential
declare readonly _serviceBrand: undefined;
private _onDidChangePassword = this._register(new Emitter<void>());
private _onDidChangePassword = this._register(new Emitter<ICredentialsChangeEvent>());
readonly onDidChangePassword = this._onDidChangePassword.event;
private credentialsProvider: ICredentialsProvider;
@@ -35,13 +35,13 @@ export class BrowserCredentialsService extends Disposable implements ICredential
async setPassword(service: string, account: string, password: string): Promise<void> {
await this.credentialsProvider.setPassword(service, account, password);
this._onDidChangePassword.fire();
this._onDidChangePassword.fire({ service, account });
}
deletePassword(service: string, account: string): Promise<boolean> {
const didDelete = this.credentialsProvider.deletePassword(service, account);
if (didDelete) {
this._onDidChangePassword.fire();
this._onDidChangePassword.fire({ service, account });
}
return didDelete;

View File

@@ -16,7 +16,12 @@ export interface ICredentialsProvider {
findCredentials(service: string): Promise<Array<{ account: string, password: string }>>;
}
export interface ICredentialsChangeEvent {
service: string
account: string;
}
export interface ICredentialsService extends ICredentialsProvider {
readonly _serviceBrand: undefined;
readonly onDidChangePassword: Event<void>;
readonly onDidChangePassword: Event<ICredentialsChangeEvent>;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
import { ICredentialsChangeEvent, ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Emitter } from 'vs/base/common/event';
@@ -13,7 +13,7 @@ export class KeytarCredentialsService extends Disposable implements ICredentials
declare readonly _serviceBrand: undefined;
private _onDidChangePassword: Emitter<void> = this._register(new Emitter());
private _onDidChangePassword: Emitter<ICredentialsChangeEvent> = this._register(new Emitter());
readonly onDidChangePassword = this._onDidChangePassword.event;
constructor(@INativeHostService private readonly nativeHostService: INativeHostService) {

View File

@@ -20,11 +20,12 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import Severity from 'vs/base/common/severity';
import { coalesce, distinct } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { compareIgnoreCase, trim } from 'vs/base/common/strings';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { Schemas } from 'vs/base/common/network';
import { PLAINTEXT_EXTENSION } from 'vs/editor/common/modes/modesRegistry';
export abstract class AbstractFileDialogService implements IFileDialogService {
@@ -275,7 +276,9 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
// Build the file filter by using our known languages
const ext: string | undefined = defaultUri ? resources.extname(defaultUri) : undefined;
let matchingFilter: IFilter | undefined;
const registeredLanguageFilters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => {
const registeredLanguageNames = this.modeService.getRegisteredLanguageNames().sort((a, b) => compareIgnoreCase(a, b));
const registeredLanguageFilters: IFilter[] = coalesce(registeredLanguageNames.map(languageName => {
const extensions = this.modeService.getExtensions(languageName);
if (!extensions || !extensions.length) {
return null;
@@ -283,10 +286,10 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
const filter: IFilter = { name: languageName, extensions: distinct(extensions).slice(0, 10).map(e => trim(e, '.')) };
if (ext && extensions.indexOf(ext) >= 0) {
if (!matchingFilter && extensions.indexOf(ext || PLAINTEXT_EXTENSION /* https://github.com/microsoft/vscode/issues/115860 */) >= 0) {
matchingFilter = filter;
return null; // matching filter will be added last to the top
return null; // first matching filter will be added to the top
}
return filter;

View File

@@ -34,6 +34,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { SaveReason } from 'vs/workbench/common/editor';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
export namespace OpenLocalFileCommand {
export const ID = 'workbench.action.files.openLocalFile';
@@ -138,6 +139,7 @@ export class SimpleFileDialog {
@IPathService protected readonly pathService: IPathService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
) {
this.remoteAuthority = this.environmentService.remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
@@ -642,12 +644,16 @@ export class SimpleFileDialog {
return true;
} else if (force && (!equalsIgnoreCase(this.basenameWithTrailingSlash(quickPickItem.uri), (this.userEnteredPathSegment + this.autoCompletePathSegment)))) {
this.userEnteredPathSegment = '';
this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename);
if (!this.accessibilityService.isScreenReaderOptimized()) {
this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename);
}
this.activeItem = quickPickItem;
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder, true).length, this.filePickBox.value.length];
// use insert text to preserve undo buffer
this.insertText(this.pathAppend(this.currentFolder, this.autoCompletePathSegment), this.autoCompletePathSegment);
this.filePickBox.valueSelection = [this.filePickBox.value.length - this.autoCompletePathSegment.length, this.filePickBox.value.length];
if (!this.accessibilityService.isScreenReaderOptimized()) {
this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder, true).length, this.filePickBox.value.length];
// use insert text to preserve undo buffer
this.insertText(this.pathAppend(this.currentFolder, this.autoCompletePathSegment), this.autoCompletePathSegment);
this.filePickBox.valueSelection = [this.filePickBox.value.length - this.autoCompletePathSegment.length, this.filePickBox.value.length];
}
return true;
} else {
this.userEnteredPathSegment = startingBasename;

View File

@@ -8,17 +8,19 @@ import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServ
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TextEditorOptions } from 'vs/workbench/common/editor';
import { IWorkbenchEditorConfiguration, TextEditorOptions } from 'vs/workbench/common/editor';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isEqual } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class CodeEditorService extends CodeEditorServiceImpl {
constructor(
@IEditorService private readonly editorService: IEditorService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super(themeService);
}
@@ -71,11 +73,13 @@ export class CodeEditorService extends CodeEditorServiceImpl {
private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: we want to detect the request to open an editor that
// is different from the current one to decide wether the current editor
// is different from the current one to decide whether the current editor
// should be pinned or not. This ensures that the source of a navigation
// is not being replaced by the target. An example is "Goto definition"
// that otherwise would replace the editor everytime the user navigates.
const enablePreviewFromCodeNavigation = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench?.editor?.enablePreviewFromCodeNavigation;
if (
!enablePreviewFromCodeNavigation && // we only need to do this if the configuration requires it
source && // we need to know the origin of the navigation
!input.options?.pinned && // we only need to look at preview editors that open
!sideBySide && // we only need to care if editor opens in same group

View File

@@ -11,7 +11,7 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn
import { Registry } from 'vs/platform/registry/common/platform';
import { ResourceMap } from 'vs/base/common/map';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
@@ -251,8 +251,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
if (this.uriIdentityService.extUri.isEqual(source, resource)) {
targetResource = target; // file got moved
} else {
const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive);
const index = indexOfPath(resource.path, source.path, ignoreCase);
const index = indexOfPath(resource.path, source.path, this.uriIdentityService.extUri.ignorePathCasing(resource));
targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved
}
@@ -757,6 +756,26 @@ export class EditorService extends Disposable implements EditorServiceImpl {
//#endregion
//#region findEditor()
findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): IEditorInput[] {
if (!this.isOpen({ resource })) {
return [];
}
const canonicalResource = this.asCanonicalEditorResource(resource);
const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
if (!targetGroup) {
return [];
}
return targetGroup.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => {
return editor.resource && isEqual(editor.resource, canonicalResource);
});
}
//#endregion
//#region replaceEditors()
async replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
@@ -1323,6 +1342,8 @@ export class DelegatingEditorService implements IEditorService {
isOpen(editor: IResourceEditorInput): boolean;
isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); }
findEditors(resource: URI, group: IEditorGroup | GroupIdentifier) { return this.editorService.findEditors(resource, group); }
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); }
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { return this.editorService.getEditorOverrides(resource, options, group); }

View File

@@ -9,14 +9,16 @@ import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platf
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { ICustomEditorInfo, IEditorService, IOpenEditorOverrideHandler, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService';
import { IEditorInput, IEditorPane, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorResourceAccessor } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorPane, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorResourceAccessor, EditorOptions } from 'vs/workbench/common/editor';
import { ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorGroup, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroup, IEditorGroupsService, OpenEditorContext, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IKeyMods, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri';
import { extname, basename, isEqual } from 'vs/base/common/resources';
import { Codicon } from 'vs/base/common/codicons';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { firstOrDefault } from 'vs/base/common/arrays';
/**
* Id of the default editor for open with.
@@ -30,14 +32,17 @@ export const DEFAULT_EDITOR_ID = 'default';
* @param id Id of the editor to use. If not provided, the user is prompted for which editor to use.
*/
export async function openEditorWith(
accessor: ServicesAccessor,
input: IEditorInput,
id: string | undefined,
options: IEditorOptions | ITextEditorOptions | undefined,
group: IEditorGroup,
editorService: IEditorService,
configurationService: IConfigurationService,
quickInputService: IQuickInputService,
): Promise<IEditorPane | undefined> {
const editorService = accessor.get(IEditorService);
const editorGroupsService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
const quickInputService = accessor.get(IQuickInputService);
const resource = input.resource;
if (!resource) {
return;
@@ -77,51 +82,94 @@ export async function openEditorWith(
}] : undefined
};
});
type QuickPickItem = IQuickPickItem & {
readonly handler: IOpenEditorOverrideHandler;
};
const picker = quickInputService.createQuickPick<(IQuickPickItem & { handler: IOpenEditorOverrideHandler })>();
const picker = quickInputService.createQuickPick<QuickPickItem>();
picker.items = items;
if (items.length) {
picker.selectedItems = [items[0]];
}
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(originalResource));
picker.canAcceptInBackground = true;
const pickedItem = await new Promise<(IQuickPickItem & { handler: IOpenEditorOverrideHandler }) | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined);
picker.dispose();
});
type PickedResult = {
readonly item: QuickPickItem;
readonly keyMods?: IKeyMods;
readonly openInBackground: boolean;
};
picker.onDidTriggerItemButton(e => {
const pick = e.item;
const id = pick.id;
resolve(pick); // open the view
picker.dispose();
function openEditor(picked: PickedResult) {
const targetGroup = getTargetGroup(group, picked.keyMods, configurationService, editorGroupsService);
// And persist the setting
if (pick && id) {
const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt };
const currentAssociations = [...configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsSettingId)];
const openOptions: IEditorOptions = {
...options,
override: picked.item.id,
preserveFocus: picked.openInBackground || options?.preserveFocus,
};
return picked.item.handler.open(input, openOptions, targetGroup, OpenEditorContext.NEW_EDITOR)?.override;
}
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.filenamePattern === newAssociation.filenamePattern) {
currentAssociations.splice(i, 1, newAssociation);
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
return;
let picked: PickedResult | undefined;
try {
picked = await new Promise<PickedResult | undefined>(resolve => {
picker.onDidAccept(e => {
if (picker.selectedItems.length === 1) {
const result: PickedResult = {
item: picker.selectedItems[0],
keyMods: picker.keyMods,
openInBackground: e.inBackground
};
if (e.inBackground) {
openEditor(result);
} else {
resolve(result);
}
} else {
resolve(undefined);
}
});
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
}
picker.onDidTriggerItemButton(e => {
const pick = e.item;
const id = pick.id;
resolve({ item: pick, openInBackground: false }); // open the view
picker.dispose();
// And persist the setting
if (pick && id) {
const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt };
const currentAssociations = [...configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsSettingId)];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.filenamePattern === newAssociation.filenamePattern) {
currentAssociations.splice(i, 1, newAssociation);
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
return;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
}
});
picker.show();
});
} finally {
picker.dispose();
}
picker.show();
});
if (!picked) {
return undefined;
}
return pickedItem?.handler.open(input, { ...options, override: pickedItem.id }, group, OpenEditorContext.NEW_EDITOR)?.override;
return openEditor(picked);
}
const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in");
@@ -132,6 +180,23 @@ export const defaultEditorOverrideEntry = Object.freeze({
detail: builtinProviderDisplayName
});
/**
* Get the group to open the editor in by looking at the pressed keys from the picker.
*/
function getTargetGroup(
startingGroup: IEditorGroup,
keyMods: IKeyMods | undefined,
configurationService: IConfigurationService,
editorGroupsService: IEditorGroupsService,
) {
if (keyMods?.alt || keyMods?.ctrlCmd) {
const direction = preferredSideBySideGroupDirection(configurationService);
const targetGroup = editorGroupsService.findGroup({ direction }, startingGroup.id);
return targetGroup ?? editorGroupsService.addGroup(startingGroup, direction);
}
return startingGroup;
}
/**
* Get a list of all available editors, including the default text editor.
*/
@@ -155,7 +220,21 @@ export function getAllAvailableEditors(
const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true });
const textOptions: IEditorOptions | ITextEditorOptions = options ? { ...options, override: false } : { override: false };
return { override: editorService.openEditor(fileEditorInput, textOptions, group) };
return {
override: (async () => {
// Try to replace existing editors for resource
const existingEditor = firstOrDefault(editorService.findEditors(resource, group));
if (existingEditor && !fileEditorInput.matches(existingEditor)) {
await editorService.replaceEditors([{
editor: existingEditor,
replacement: fileEditorInput,
options: options ? EditorOptions.create(options) : undefined,
}], group);
}
return editorService.openEditor(fileEditorInput, textOptions, group);
})()
};
}
},
{
@@ -169,7 +248,7 @@ export function getAllAvailableEditors(
export const customEditorsAssociationsSettingId = 'workbench.editorAssociations';
export const viewTypeSchamaAddition: IJSONSchema = {
export const viewTypeSchemaAddition: IJSONSchema = {
type: 'string',
enum: []
};
@@ -202,7 +281,7 @@ export const editorAssociationsConfigurationNode: IConfigurationNode = {
type: 'string',
description: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
},
viewTypeSchamaAddition
viewTypeSchemaAddition
]
},
'filenamePattern': {
@@ -222,8 +301,8 @@ export const DEFAULT_CUSTOM_EDITOR: ICustomEditorInfo = {
};
export function updateViewTypeSchema(enumValues: string[], enumDescriptions: string[]): void {
viewTypeSchamaAddition.enum = enumValues;
viewTypeSchamaAddition.enumDescriptions = enumDescriptions;
viewTypeSchemaAddition.enum = enumValues;
viewTypeSchemaAddition.enumDescriptions = enumDescriptions;
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);

View File

@@ -175,7 +175,7 @@ export interface IEditorService {
* identifier.
*
* @param order the order of the editors to use
* @param options wether to exclude sticky editors or not
* @param options whether to exclude sticky editors or not
*/
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
@@ -233,6 +233,11 @@ export interface IEditorService {
isOpen(editor: IResourceEditorInput): boolean;
isOpen(editor: IEditorInput): boolean;
/**
* Find the existing editors for a given resource.
*/
findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): IEditorInput[];
/**
* Get all available editor overrides for the editor input.
*/

View File

@@ -94,36 +94,36 @@ suite('EditorService', () => {
// Open input
let editor = await service.openEditor(input, { pinned: true });
assert.equal(editor?.getId(), TEST_EDITOR_ID);
assert.equal(editor, service.activeEditorPane);
assert.equal(1, service.count);
assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor);
assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor);
assert.equal(input, service.activeEditor);
assert.equal(service.visibleEditorPanes.length, 1);
assert.equal(service.visibleEditorPanes[0], editor);
assert.strictEqual(editor?.getId(), TEST_EDITOR_ID);
assert.strictEqual(editor, service.activeEditorPane);
assert.strictEqual(1, service.count);
assert.strictEqual(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor);
assert.strictEqual(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor);
assert.strictEqual(input, service.activeEditor);
assert.strictEqual(service.visibleEditorPanes.length, 1);
assert.strictEqual(service.visibleEditorPanes[0], editor);
assert.ok(!service.activeTextEditorControl);
assert.ok(!service.activeTextEditorMode);
assert.equal(service.visibleTextEditorControls.length, 0);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen({ resource: input.resource }), true);
assert.equal(activeEditorChangeEventCounter, 1);
assert.equal(visibleEditorChangeEventCounter, 1);
assert.strictEqual(service.visibleTextEditorControls.length, 0);
assert.strictEqual(service.isOpen(input), true);
assert.strictEqual(service.isOpen({ resource: input.resource }), true);
assert.strictEqual(activeEditorChangeEventCounter, 1);
assert.strictEqual(visibleEditorChangeEventCounter, 1);
// Close input
await editor?.group?.closeEditor(input);
assert.equal(0, service.count);
assert.equal(0, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length);
assert.equal(0, service.getEditors(EditorsOrder.SEQUENTIAL).length);
assert.equal(didCloseEditorListenerCounter, 1);
assert.equal(activeEditorChangeEventCounter, 2);
assert.equal(visibleEditorChangeEventCounter, 2);
assert.strictEqual(0, service.count);
assert.strictEqual(0, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length);
assert.strictEqual(0, service.getEditors(EditorsOrder.SEQUENTIAL).length);
assert.strictEqual(didCloseEditorListenerCounter, 1);
assert.strictEqual(activeEditorChangeEventCounter, 2);
assert.strictEqual(visibleEditorChangeEventCounter, 2);
assert.ok(input.gotDisposed);
// Open again 2 inputs (disposed editors are ignored!)
await service.openEditor(input, { pinned: true });
assert.equal(0, service.count);
assert.strictEqual(0, service.count);
// Open again 2 inputs (recreate because disposed)
input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID);
@@ -132,40 +132,40 @@ suite('EditorService', () => {
await service.openEditor(input, { pinned: true });
editor = await service.openEditor(otherInput, { pinned: true });
assert.equal(2, service.count);
assert.equal(otherInput, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor);
assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor);
assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor);
assert.equal(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor);
assert.equal(service.visibleEditorPanes.length, 1);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen({ resource: input.resource }), true);
assert.equal(service.isOpen(otherInput), true);
assert.equal(service.isOpen({ resource: otherInput.resource }), true);
assert.strictEqual(2, service.count);
assert.strictEqual(otherInput, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor);
assert.strictEqual(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor);
assert.strictEqual(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor);
assert.strictEqual(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor);
assert.strictEqual(service.visibleEditorPanes.length, 1);
assert.strictEqual(service.isOpen(input), true);
assert.strictEqual(service.isOpen({ resource: input.resource }), true);
assert.strictEqual(service.isOpen(otherInput), true);
assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true);
assert.equal(activeEditorChangeEventCounter, 4);
assert.equal(visibleEditorChangeEventCounter, 4);
assert.strictEqual(activeEditorChangeEventCounter, 4);
assert.strictEqual(visibleEditorChangeEventCounter, 4);
const stickyInput = new TestFileEditorInput(URI.parse('my://resource3-basics'), TEST_EDITOR_INPUT_ID);
await service.openEditor(stickyInput, { sticky: true });
assert.equal(3, service.count);
assert.strictEqual(3, service.count);
const allSequentialEditors = service.getEditors(EditorsOrder.SEQUENTIAL);
assert.equal(allSequentialEditors.length, 3);
assert.equal(stickyInput, allSequentialEditors[0].editor);
assert.equal(input, allSequentialEditors[1].editor);
assert.equal(otherInput, allSequentialEditors[2].editor);
assert.strictEqual(allSequentialEditors.length, 3);
assert.strictEqual(stickyInput, allSequentialEditors[0].editor);
assert.strictEqual(input, allSequentialEditors[1].editor);
assert.strictEqual(otherInput, allSequentialEditors[2].editor);
const sequentialEditorsExcludingSticky = service.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true });
assert.equal(sequentialEditorsExcludingSticky.length, 2);
assert.equal(input, sequentialEditorsExcludingSticky[0].editor);
assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor);
assert.strictEqual(sequentialEditorsExcludingSticky.length, 2);
assert.strictEqual(input, sequentialEditorsExcludingSticky[0].editor);
assert.strictEqual(otherInput, sequentialEditorsExcludingSticky[1].editor);
const mruEditorsExcludingSticky = service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true });
assert.equal(mruEditorsExcludingSticky.length, 2);
assert.equal(input, sequentialEditorsExcludingSticky[0].editor);
assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor);
assert.strictEqual(mruEditorsExcludingSticky.length, 2);
assert.strictEqual(input, sequentialEditorsExcludingSticky[0].editor);
assert.strictEqual(otherInput, sequentialEditorsExcludingSticky[1].editor);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
@@ -184,39 +184,39 @@ suite('EditorService', () => {
await part.whenRestored;
const editor1 = await service.openEditor(sideBySideInput, { pinned: true });
assert.equal(part.activeGroup.count, 1);
assert.strictEqual(part.activeGroup.count, 1);
assert.equal(service.isOpen(input), false);
assert.equal(service.isOpen(otherInput), false);
assert.equal(service.isOpen(sideBySideInput), true);
assert.equal(service.isOpen({ resource: input.resource }), false);
assert.equal(service.isOpen({ resource: otherInput.resource }), true);
assert.strictEqual(service.isOpen(input), false);
assert.strictEqual(service.isOpen(otherInput), false);
assert.strictEqual(service.isOpen(sideBySideInput), true);
assert.strictEqual(service.isOpen({ resource: input.resource }), false);
assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true);
const editor2 = await service.openEditor(input, { pinned: true });
assert.equal(part.activeGroup.count, 2);
assert.strictEqual(part.activeGroup.count, 2);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen(otherInput), false);
assert.equal(service.isOpen(sideBySideInput), true);
assert.equal(service.isOpen({ resource: input.resource }), true);
assert.equal(service.isOpen({ resource: otherInput.resource }), true);
assert.strictEqual(service.isOpen(input), true);
assert.strictEqual(service.isOpen(otherInput), false);
assert.strictEqual(service.isOpen(sideBySideInput), true);
assert.strictEqual(service.isOpen({ resource: input.resource }), true);
assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true);
await editor2?.group?.closeEditor(input);
assert.equal(part.activeGroup.count, 1);
assert.strictEqual(part.activeGroup.count, 1);
assert.equal(service.isOpen(input), false);
assert.equal(service.isOpen(otherInput), false);
assert.equal(service.isOpen(sideBySideInput), true);
assert.equal(service.isOpen({ resource: input.resource }), false);
assert.equal(service.isOpen({ resource: otherInput.resource }), true);
assert.strictEqual(service.isOpen(input), false);
assert.strictEqual(service.isOpen(otherInput), false);
assert.strictEqual(service.isOpen(sideBySideInput), true);
assert.strictEqual(service.isOpen({ resource: input.resource }), false);
assert.strictEqual(service.isOpen({ resource: otherInput.resource }), true);
await editor1?.group?.closeEditor(sideBySideInput);
assert.equal(service.isOpen(input), false);
assert.equal(service.isOpen(otherInput), false);
assert.equal(service.isOpen(sideBySideInput), false);
assert.equal(service.isOpen({ resource: input.resource }), false);
assert.equal(service.isOpen({ resource: otherInput.resource }), false);
assert.strictEqual(service.isOpen(input), false);
assert.strictEqual(service.isOpen(otherInput), false);
assert.strictEqual(service.isOpen(sideBySideInput), false);
assert.strictEqual(service.isOpen({ resource: input.resource }), false);
assert.strictEqual(service.isOpen({ resource: otherInput.resource }), false);
part.dispose();
});
@@ -232,12 +232,12 @@ suite('EditorService', () => {
// Open editors
await service.openEditors([{ editor: input }, { editor: otherInput }]);
assert.equal(part.activeGroup.count, 2);
assert.strictEqual(part.activeGroup.count, 2);
// Replace editors
await service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup);
assert.equal(part.activeGroup.count, 2);
assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0);
assert.strictEqual(part.activeGroup.count, 2);
assert.strictEqual(part.activeGroup.getIndexOfEditor(replaceInput), 0);
part.dispose();
});
@@ -255,17 +255,17 @@ suite('EditorService', () => {
const fileEditorInput2 = service.createEditorInput({ resource: fileResource2 });
assert.ok(fileEditorInput2);
assert.notEqual(fileEditorInput1, fileEditorInput2);
assert.notStrictEqual(fileEditorInput1, fileEditorInput2);
const fileEditorInput1Again = service.createEditorInput({ resource: fileResource1 });
assert.equal(fileEditorInput1Again, fileEditorInput1);
assert.strictEqual(fileEditorInput1Again, fileEditorInput1);
fileEditorInput1Again.dispose();
assert.ok(fileEditorInput1.isDisposed());
const fileEditorInput1AgainAndAgain = service.createEditorInput({ resource: fileResource1 });
assert.notEqual(fileEditorInput1AgainAndAgain, fileEditorInput1);
assert.notStrictEqual(fileEditorInput1AgainAndAgain, fileEditorInput1);
assert.ok(!fileEditorInput1AgainAndAgain.isDisposed());
// Cached Input (Resource)
@@ -277,17 +277,17 @@ suite('EditorService', () => {
const input2 = service.createEditorInput({ resource: resource2 });
assert.ok(input2);
assert.notEqual(input1, input2);
assert.notStrictEqual(input1, input2);
const input1Again = service.createEditorInput({ resource: resource1 });
assert.equal(input1Again, input1);
assert.strictEqual(input1Again, input1);
input1Again.dispose();
assert.ok(input1.isDisposed());
const input1AgainAndAgain = service.createEditorInput({ resource: resource1 });
assert.notEqual(input1AgainAndAgain, input1);
assert.notStrictEqual(input1AgainAndAgain, input1);
assert.ok(!input1AgainAndAgain.isDisposed());
});
@@ -311,34 +311,34 @@ suite('EditorService', () => {
let inputDifferentCase = service.createEditorInput({ resource: toResource.call(this, '/INDEX.html') });
if (!isLinux) {
assert.equal(input, inputDifferentCase);
assert.equal(input.resource?.toString(), inputDifferentCase.resource?.toString());
assert.strictEqual(input, inputDifferentCase);
assert.strictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString());
} else {
assert.notEqual(input, inputDifferentCase);
assert.notEqual(input.resource?.toString(), inputDifferentCase.resource?.toString());
assert.notStrictEqual(input, inputDifferentCase);
assert.notStrictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString());
}
// Typed Input
assert.equal(service.createEditorInput(input), input);
assert.equal(service.createEditorInput({ editor: input }), input);
assert.strictEqual(service.createEditorInput(input), input);
assert.strictEqual(service.createEditorInput({ editor: input }), input);
// Untyped Input (file, encoding)
input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredEncoding(), 'utf16le');
assert.strictEqual(contentInput.getPreferredEncoding(), 'utf16le');
// Untyped Input (file, mode)
input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredMode(), mode);
assert.strictEqual(contentInput.getPreferredMode(), mode);
// Untyped Input (file, different mode)
input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode: 'text' });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredMode(), 'text');
assert.strictEqual(contentInput.getPreferredMode(), 'text');
// Untyped Input (untitled)
input = service.createEditorInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } });
@@ -348,13 +348,13 @@ suite('EditorService', () => {
input = service.createEditorInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledTextEditorInput);
let model = await input.resolve() as UntitledTextEditorModel;
assert.equal(model.textEditorModel?.getValue(), 'Hello Untitled');
assert.strictEqual(model.textEditorModel?.getValue(), 'Hello Untitled');
// Untyped Input (untitled with mode)
input = service.createEditorInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledTextEditorInput);
model = await input.resolve() as UntitledTextEditorModel;
assert.equal(model.getMode(), mode);
assert.strictEqual(model.getMode(), mode);
// Untyped Input (untitled with file path)
input = service.createEditorInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } });
@@ -434,16 +434,16 @@ suite('EditorService', () => {
await service.openEditor(input, { pinned: true }, rightGroup);
const editors = service.editors;
assert.equal(editors.length, 2);
assert.equal(editors[0], input);
assert.equal(editors[1], input);
assert.strictEqual(editors.length, 2);
assert.strictEqual(editors[0], input);
assert.strictEqual(editors[1], input);
// Close input
await rootGroup.closeEditor(input);
assert.equal(input.isDisposed(), false);
assert.strictEqual(input.isDisposed(), false);
await rightGroup.closeEditor(input);
assert.equal(input.isDisposed(), true);
assert.strictEqual(input.isDisposed(), true);
part.dispose();
});
@@ -461,15 +461,15 @@ suite('EditorService', () => {
await service.openEditor(input1, { pinned: true }, rootGroup);
let editor = await service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP);
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor?.group, part.groups[1]);
assert.strictEqual(part.activeGroup, rootGroup);
assert.strictEqual(part.count, 2);
assert.strictEqual(editor?.group, part.groups[1]);
// Open to the side uses existing neighbour group if any
editor = await service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP);
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor?.group, part.groups[1]);
assert.strictEqual(part.activeGroup, rootGroup);
assert.strictEqual(part.count, 2);
assert.strictEqual(editor?.group, part.groups[1]);
part.dispose();
});
@@ -488,23 +488,23 @@ suite('EditorService', () => {
let editor = await service.openEditor(input2, { pinned: true, preserveFocus: true, activation: EditorActivation.ACTIVATE }, SIDE_GROUP);
const sideGroup = editor?.group;
assert.equal(part.activeGroup, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.PRESERVE }, rootGroup);
assert.equal(part.activeGroup, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.ACTIVATE }, rootGroup);
assert.equal(part.activeGroup, rootGroup);
assert.strictEqual(part.activeGroup, rootGroup);
editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.PRESERVE }, sideGroup);
assert.equal(part.activeGroup, rootGroup);
assert.strictEqual(part.activeGroup, rootGroup);
editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.ACTIVATE }, sideGroup);
assert.equal(part.activeGroup, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup);
assert.equal(part.activeGroup, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
part.dispose();
});
@@ -526,12 +526,12 @@ suite('EditorService', () => {
});
function assertActiveEditorChangedEvent(expected: boolean) {
assert.equal(activeEditorChangeEventFired, expected, `Unexpected active editor change state (got ${activeEditorChangeEventFired}, expected ${expected})`);
assert.strictEqual(activeEditorChangeEventFired, expected, `Unexpected active editor change state (got ${activeEditorChangeEventFired}, expected ${expected})`);
activeEditorChangeEventFired = false;
}
function assertVisibleEditorsChangedEvent(expected: boolean) {
assert.equal(visibleEditorChangeEventFired, expected, `Unexpected visible editors change state (got ${visibleEditorChangeEventFired}, expected ${expected})`);
assert.strictEqual(visibleEditorChangeEventFired, expected, `Unexpected visible editors change state (got ${visibleEditorChangeEventFired}, expected ${expected})`);
visibleEditorChangeEventFired = false;
}
@@ -740,7 +740,7 @@ suite('EditorService', () => {
});
function assertActiveEditorChangedEvent(expected: number) {
assert.equal(activeEditorChangeEvents, expected, `Unexpected active editor change state (got ${activeEditorChangeEvents}, expected ${expected})`);
assert.strictEqual(activeEditorChangeEvents, expected, `Unexpected active editor change state (got ${activeEditorChangeEvents}, expected ${expected})`);
activeEditorChangeEvents = 0;
}
@@ -774,9 +774,9 @@ suite('EditorService', () => {
// Open untitled input
let editor = await service.openEditor({});
assert.equal(service.activeEditorPane, editor);
assert.equal(service.activeTextEditorControl, editor?.getControl());
assert.equal(service.activeTextEditorMode, 'plaintext');
assert.strictEqual(service.activeEditorPane, editor);
assert.strictEqual(service.activeTextEditorControl, editor?.getControl());
assert.strictEqual(service.activeTextEditorMode, 'plaintext');
part.dispose();
});
@@ -822,7 +822,7 @@ suite('EditorService', () => {
await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP);
await service.save({ groupId: rootGroup.id, editor: input1 });
assert.equal(input1.gotSaved, true);
assert.strictEqual(input1.gotSaved, true);
input1.gotSaved = false;
input1.gotSavedAs = false;
@@ -833,7 +833,7 @@ suite('EditorService', () => {
sameInput1.dirty = true;
await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true });
assert.equal(input1.gotSavedAs, true);
assert.strictEqual(input1.gotSavedAs, true);
input1.gotSaved = false;
input1.gotSavedAs = false;
@@ -844,8 +844,8 @@ suite('EditorService', () => {
sameInput1.dirty = true;
const revertRes = await service.revertAll();
assert.equal(revertRes, true);
assert.equal(input1.gotReverted, true);
assert.strictEqual(revertRes, true);
assert.strictEqual(input1.gotReverted, true);
input1.gotSaved = false;
input1.gotSavedAs = false;
@@ -856,9 +856,9 @@ suite('EditorService', () => {
sameInput1.dirty = true;
const saveRes = await service.saveAll();
assert.equal(saveRes, true);
assert.equal(input1.gotSaved, true);
assert.equal(input2.gotSaved, true);
assert.strictEqual(saveRes, true);
assert.strictEqual(input1.gotSaved, true);
assert.strictEqual(input2.gotSaved, true);
input1.gotSaved = false;
input1.gotSavedAs = false;
@@ -873,13 +873,13 @@ suite('EditorService', () => {
await service.saveAll({ saveAs: true });
assert.equal(input1.gotSavedAs, true);
assert.equal(input2.gotSavedAs, true);
assert.strictEqual(input1.gotSavedAs, true);
assert.strictEqual(input2.gotSavedAs, true);
// services dedupes inputs automatically
assert.equal(sameInput1.gotSaved, false);
assert.equal(sameInput1.gotSavedAs, false);
assert.equal(sameInput1.gotReverted, false);
assert.strictEqual(sameInput1.gotSaved, false);
assert.strictEqual(sameInput1.gotSavedAs, false);
assert.strictEqual(sameInput1.gotReverted, false);
part.dispose();
});
@@ -901,9 +901,9 @@ suite('EditorService', () => {
await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP);
const revertRes = await service.revertAll({ excludeSticky: true });
assert.equal(revertRes, true);
assert.equal(input1.gotReverted, false);
assert.equal(sameInput1.gotReverted, true);
assert.strictEqual(revertRes, true);
assert.strictEqual(input1.gotReverted, false);
assert.strictEqual(sameInput1.gotReverted, true);
input1.gotSaved = false;
input1.gotSavedAs = false;
@@ -918,10 +918,10 @@ suite('EditorService', () => {
sameInput1.dirty = true;
const saveRes = await service.saveAll({ excludeSticky: true });
assert.equal(saveRes, true);
assert.equal(input1.gotSaved, false);
assert.equal(input2.gotSaved, true);
assert.equal(sameInput1.gotSaved, true);
assert.strictEqual(saveRes, true);
assert.strictEqual(input1.gotSaved, false);
assert.strictEqual(input2.gotSaved, true);
assert.strictEqual(sameInput1.gotSaved, true);
part.dispose();
});
@@ -949,7 +949,7 @@ suite('EditorService', () => {
await service.openEditor(input1, { pinned: true });
await service.openEditor(input2, { pinned: true });
assert.equal(rootGroup.activeEditor, input2);
assert.strictEqual(rootGroup.activeEditor, input2);
const activeEditorChangePromise = awaitActiveEditorChange(service);
accessor.fileService.fireAfterOperation(new FileOperationEvent(input2.resource, FileOperation.DELETE));
@@ -958,9 +958,9 @@ suite('EditorService', () => {
}
if (dirty) {
assert.equal(rootGroup.activeEditor, input2);
assert.strictEqual(rootGroup.activeEditor, input2);
} else {
assert.equal(rootGroup.activeEditor, input1);
assert.strictEqual(rootGroup.activeEditor, input1);
}
part.dispose();
@@ -993,15 +993,13 @@ suite('EditorService', () => {
}));
await activeEditorChangePromise;
assert.equal(rootGroup.activeEditor, movedInput);
assert.strictEqual(rootGroup.activeEditor, movedInput);
part.dispose();
});
function awaitActiveEditorChange(editorService: IEditorService): Promise<void> {
return new Promise(c => {
Event.once(editorService.onDidActiveEditorChange)(c);
});
return Event.toPromise(Event.once(editorService.onDidActiveEditorChange));
}
test('file watcher gets installed for out of workspace files', async function () {
@@ -1013,15 +1011,15 @@ suite('EditorService', () => {
await part.whenRestored;
await service.openEditor(input1, { pinned: true });
assert.equal(accessor.fileService.watches.length, 1);
assert.equal(accessor.fileService.watches[0].toString(), input1.resource.toString());
assert.strictEqual(accessor.fileService.watches.length, 1);
assert.strictEqual(accessor.fileService.watches[0].toString(), input1.resource.toString());
const editor = await service.openEditor(input2, { pinned: true });
assert.equal(accessor.fileService.watches.length, 1);
assert.equal(accessor.fileService.watches[0].toString(), input2.resource.toString());
assert.strictEqual(accessor.fileService.watches.length, 1);
assert.strictEqual(accessor.fileService.watches[0].toString(), input2.resource.toString());
await editor?.group?.closeAllEditors();
assert.equal(accessor.fileService.watches.length, 0);
assert.strictEqual(accessor.fileService.watches.length, 0);
part.dispose();
});
@@ -1069,7 +1067,7 @@ suite('EditorService', () => {
await service.openEditor(input1, { pinned: true });
assert.ok(overrideCalled);
assert.equal(service.activeEditor, input2);
assert.strictEqual(service.activeEditor, input2);
handler.dispose();
part.dispose();
@@ -1094,4 +1092,52 @@ suite('EditorService', () => {
part.dispose();
});
test('findEditors', async () => {
const [part, service] = createEditorService();
const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID);
const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID);
await part.whenRestored;
// Open editors
await service.openEditors([{ editor: input }, { editor: otherInput }]);
assert.strictEqual(part.activeGroup.count, 2);
// Try using find editors for opened editors
{
const found = service.findEditors(input.resource, part.activeGroup);
assert.strictEqual(found.length, 1);
assert.strictEqual(found[0], input);
}
{
const found = service.findEditors(otherInput.resource, part.activeGroup);
assert.strictEqual(found.length, 1);
assert.strictEqual(found[0], otherInput);
}
// Make sure we don't find non-opened editors
{
const found = service.findEditors(URI.parse('my://no-such-resource'), part.activeGroup);
assert.strictEqual(found.length, 0);
}
// Make sure we don't find editors across groups
{
const newEditor = await service.openEditor(new TestFileEditorInput(URI.parse('my://other-group-resource'), TEST_EDITOR_INPUT_ID), { pinned: true, preserveFocus: true }, SIDE_GROUP);
const found = service.findEditors(input.resource, newEditor!.group!.id);
assert.strictEqual(found.length, 0);
}
// Check we don't find editors after closing them
await part.activeGroup.closeAllEditors();
{
const found = service.findEditors(input.resource, part.activeGroup);
assert.strictEqual(found.length, 0);
}
part.dispose();
});
});

View File

@@ -17,7 +17,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
import { timeout } from 'vs/base/common/async';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { isWeb } from 'vs/base/common/platform';
const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver';
const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver';
@@ -66,19 +65,19 @@ suite('EditorsObserver', function () {
});
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 0);
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, false);
assert.strictEqual(currentEditorsMRU.length, 0);
assert.strictEqual(onDidMostRecentlyActiveEditorsChangeCalled, false);
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 1);
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true);
assert.equal(observer.hasEditor(input1.resource), true);
assert.strictEqual(currentEditorsMRU.length, 1);
assert.strictEqual(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(onDidMostRecentlyActiveEditorsChangeCalled, true);
assert.strictEqual(observer.hasEditor(input1.resource), true);
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
@@ -87,50 +86,50 @@ suite('EditorsObserver', function () {
await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[0].editor, input2);
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[1].editor, input3);
assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input2);
assert.strictEqual(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input3);
assert.strictEqual(currentEditorsMRU[2].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
onDidMostRecentlyActiveEditorsChangeCalled = false;
await part.activeGroup.closeEditor(input1);
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 2);
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[0].editor, input2);
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.equal(currentEditorsMRU[1].editor, input3);
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 2);
assert.strictEqual(currentEditorsMRU[0].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input2);
assert.strictEqual(currentEditorsMRU[1].groupId, part.activeGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input3);
assert.strictEqual(onDidMostRecentlyActiveEditorsChangeCalled, true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
await part.activeGroup.closeAllEditors();
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 0);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), false);
assert.equal(observer.hasEditor(input3.resource), false);
assert.strictEqual(currentEditorsMRU.length, 0);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), false);
assert.strictEqual(observer.hasEditor(input3.resource), false);
part.dispose();
listener.dispose();
@@ -142,7 +141,7 @@ suite('EditorsObserver', function () {
const rootGroup = part.activeGroup;
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 0);
assert.strictEqual(currentEditorsMRU.length, 0);
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
@@ -152,22 +151,22 @@ suite('EditorsObserver', function () {
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 2);
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.strictEqual(currentEditorsMRU.length, 2);
assert.strictEqual(currentEditorsMRU[0].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 2);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(currentEditorsMRU[1].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[1].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.strictEqual(currentEditorsMRU.length, 2);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(currentEditorsMRU[1].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
// Opening an editor inactive should not change
// the most recent editor, but rather put it behind
@@ -176,39 +175,36 @@ suite('EditorsObserver', function () {
await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true }));
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
await rootGroup.closeAllEditors();
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 1);
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), false);
assert.strictEqual(currentEditorsMRU.length, 1);
assert.strictEqual(currentEditorsMRU[0].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), false);
await sideGroup.closeAllEditors();
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 0);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), false);
assert.strictEqual(currentEditorsMRU.length, 0);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), false);
part.dispose();
});
test('copy group', async function () {
if (isWeb) {
this.skip();
}
const [part, observer] = await createEditorObserver();
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
@@ -222,49 +218,51 @@ suite('EditorsObserver', function () {
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT);
await copiedGroup.whenRestored;
copiedGroup.setActive(true);
copiedGroup.focus();
currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 6);
assert.equal(currentEditorsMRU[0].groupId, copiedGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input3);
assert.equal(currentEditorsMRU[2].groupId, copiedGroup.id);
assert.equal(currentEditorsMRU[2].editor, input2);
assert.equal(currentEditorsMRU[3].groupId, copiedGroup.id);
assert.equal(currentEditorsMRU[3].editor, input1);
assert.equal(currentEditorsMRU[4].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[4].editor, input2);
assert.equal(currentEditorsMRU[5].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[5].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 6);
assert.strictEqual(currentEditorsMRU[0].groupId, copiedGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input3);
assert.strictEqual(currentEditorsMRU[2].groupId, copiedGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input2);
assert.strictEqual(currentEditorsMRU[3].groupId, copiedGroup.id);
assert.strictEqual(currentEditorsMRU[3].editor, input1);
assert.strictEqual(currentEditorsMRU[4].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[4].editor, input2);
assert.strictEqual(currentEditorsMRU[5].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[5].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
await rootGroup.closeAllEditors();
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
await copiedGroup.closeAllEditors();
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), false);
assert.equal(observer.hasEditor(input3.resource), false);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), false);
assert.strictEqual(observer.hasEditor(input3.resource), false);
part.dispose();
});
@@ -287,16 +285,16 @@ suite('EditorsObserver', function () {
await part.whenRestored;
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
@@ -304,16 +302,16 @@ suite('EditorsObserver', function () {
await part.whenRestored;
currentEditorsMRU = restoredObserver.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
part.clearState();
part.dispose();
@@ -339,16 +337,16 @@ suite('EditorsObserver', function () {
await part.whenRestored;
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
@@ -356,16 +354,16 @@ suite('EditorsObserver', function () {
await part.whenRestored;
currentEditorsMRU = restoredObserver.editors;
assert.equal(currentEditorsMRU.length, 3);
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
assert.equal(currentEditorsMRU[0].editor, input3);
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[1].editor, input2);
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[2].editor, input1);
assert.equal(restoredObserver.hasEditor(input1.resource), true);
assert.equal(restoredObserver.hasEditor(input2.resource), true);
assert.equal(restoredObserver.hasEditor(input3.resource), true);
assert.strictEqual(currentEditorsMRU.length, 3);
assert.strictEqual(currentEditorsMRU[0].groupId, sideGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input3);
assert.strictEqual(currentEditorsMRU[1].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[1].editor, input2);
assert.strictEqual(currentEditorsMRU[2].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[2].editor, input1);
assert.strictEqual(restoredObserver.hasEditor(input1.resource), true);
assert.strictEqual(restoredObserver.hasEditor(input2.resource), true);
assert.strictEqual(restoredObserver.hasEditor(input3.resource), true);
part.clearState();
part.dispose();
@@ -385,10 +383,10 @@ suite('EditorsObserver', function () {
await part.whenRestored;
let currentEditorsMRU = observer.editors;
assert.equal(currentEditorsMRU.length, 1);
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
assert.equal(currentEditorsMRU[0].editor, input1);
assert.equal(observer.hasEditor(input1.resource), true);
assert.strictEqual(currentEditorsMRU.length, 1);
assert.strictEqual(currentEditorsMRU[0].groupId, rootGroup.id);
assert.strictEqual(currentEditorsMRU[0].editor, input1);
assert.strictEqual(observer.hasEditor(input1.resource), true);
storage.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
@@ -396,8 +394,8 @@ suite('EditorsObserver', function () {
await part.whenRestored;
currentEditorsMRU = restoredObserver.editors;
assert.equal(currentEditorsMRU.length, 0);
assert.equal(restoredObserver.hasEditor(input1.resource), false);
assert.strictEqual(currentEditorsMRU.length, 0);
assert.strictEqual(restoredObserver.hasEditor(input1.resource), false);
part.clearState();
part.dispose();
@@ -423,45 +421,45 @@ suite('EditorsObserver', function () {
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
assert.equal(rootGroup.count, 3);
assert.equal(rootGroup.isOpened(input1), false);
assert.equal(rootGroup.isOpened(input2), true);
assert.equal(rootGroup.isOpened(input3), true);
assert.equal(rootGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(rootGroup.count, 3);
assert.strictEqual(rootGroup.isOpened(input1), false);
assert.strictEqual(rootGroup.isOpened(input2), true);
assert.strictEqual(rootGroup.isOpened(input3), true);
assert.strictEqual(rootGroup.isOpened(input4), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
assert.strictEqual(observer.hasEditor(input4.resource), true);
input2.setDirty();
part.enforcePartOptions({ limit: { enabled: true, value: 1 } });
await timeout(0);
assert.equal(rootGroup.count, 2);
assert.equal(rootGroup.isOpened(input1), false);
assert.equal(rootGroup.isOpened(input2), true); // dirty
assert.equal(rootGroup.isOpened(input3), false);
assert.equal(rootGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), false);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(rootGroup.count, 2);
assert.strictEqual(rootGroup.isOpened(input1), false);
assert.strictEqual(rootGroup.isOpened(input2), true); // dirty
assert.strictEqual(rootGroup.isOpened(input3), false);
assert.strictEqual(rootGroup.isOpened(input4), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), false);
assert.strictEqual(observer.hasEditor(input4.resource), true);
const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID);
await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true }));
assert.equal(rootGroup.count, 1);
assert.equal(rootGroup.isOpened(input1), false);
assert.equal(rootGroup.isOpened(input2), true); // dirty
assert.equal(rootGroup.isOpened(input3), false);
assert.equal(rootGroup.isOpened(input4), false);
assert.equal(sideGroup.isOpened(input5), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), false);
assert.equal(observer.hasEditor(input4.resource), false);
assert.equal(observer.hasEditor(input5.resource), true);
assert.strictEqual(rootGroup.count, 1);
assert.strictEqual(rootGroup.isOpened(input1), false);
assert.strictEqual(rootGroup.isOpened(input2), true); // dirty
assert.strictEqual(rootGroup.isOpened(input3), false);
assert.strictEqual(rootGroup.isOpened(input4), false);
assert.strictEqual(sideGroup.isOpened(input5), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), false);
assert.strictEqual(observer.hasEditor(input4.resource), false);
assert.strictEqual(observer.hasEditor(input5.resource), true);
observer.dispose();
part.dispose();
@@ -487,51 +485,51 @@ suite('EditorsObserver', function () {
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
assert.equal(rootGroup.count, 3); // 1 editor got closed due to our limit!
assert.equal(rootGroup.isOpened(input1), false);
assert.equal(rootGroup.isOpened(input2), true);
assert.equal(rootGroup.isOpened(input3), true);
assert.equal(rootGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(rootGroup.count, 3); // 1 editor got closed due to our limit!
assert.strictEqual(rootGroup.isOpened(input1), false);
assert.strictEqual(rootGroup.isOpened(input2), true);
assert.strictEqual(rootGroup.isOpened(input3), true);
assert.strictEqual(rootGroup.isOpened(input4), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
assert.strictEqual(observer.hasEditor(input4.resource), true);
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
assert.equal(sideGroup.count, 3);
assert.equal(sideGroup.isOpened(input1), false);
assert.equal(sideGroup.isOpened(input2), true);
assert.equal(sideGroup.isOpened(input3), true);
assert.equal(sideGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), true);
assert.equal(observer.hasEditor(input3.resource), true);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(sideGroup.count, 3);
assert.strictEqual(sideGroup.isOpened(input1), false);
assert.strictEqual(sideGroup.isOpened(input2), true);
assert.strictEqual(sideGroup.isOpened(input3), true);
assert.strictEqual(sideGroup.isOpened(input4), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), true);
assert.strictEqual(observer.hasEditor(input3.resource), true);
assert.strictEqual(observer.hasEditor(input4.resource), true);
part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } });
await timeout(10);
assert.equal(rootGroup.count, 1);
assert.equal(rootGroup.isOpened(input1), false);
assert.equal(rootGroup.isOpened(input2), false);
assert.equal(rootGroup.isOpened(input3), false);
assert.equal(rootGroup.isOpened(input4), true);
assert.strictEqual(rootGroup.count, 1);
assert.strictEqual(rootGroup.isOpened(input1), false);
assert.strictEqual(rootGroup.isOpened(input2), false);
assert.strictEqual(rootGroup.isOpened(input3), false);
assert.strictEqual(rootGroup.isOpened(input4), true);
assert.equal(sideGroup.count, 1);
assert.equal(sideGroup.isOpened(input1), false);
assert.equal(sideGroup.isOpened(input2), false);
assert.equal(sideGroup.isOpened(input3), false);
assert.equal(sideGroup.isOpened(input4), true);
assert.strictEqual(sideGroup.count, 1);
assert.strictEqual(sideGroup.isOpened(input1), false);
assert.strictEqual(sideGroup.isOpened(input2), false);
assert.strictEqual(sideGroup.isOpened(input3), false);
assert.strictEqual(sideGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), false);
assert.equal(observer.hasEditor(input2.resource), false);
assert.equal(observer.hasEditor(input3.resource), false);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(observer.hasEditor(input1.resource), false);
assert.strictEqual(observer.hasEditor(input2.resource), false);
assert.strictEqual(observer.hasEditor(input3.resource), false);
assert.strictEqual(observer.hasEditor(input4.resource), true);
observer.dispose();
part.dispose();
@@ -556,15 +554,15 @@ suite('EditorsObserver', function () {
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
assert.equal(rootGroup.count, 3);
assert.equal(rootGroup.isOpened(input1), true);
assert.equal(rootGroup.isOpened(input2), false);
assert.equal(rootGroup.isOpened(input3), true);
assert.equal(rootGroup.isOpened(input4), true);
assert.equal(observer.hasEditor(input1.resource), true);
assert.equal(observer.hasEditor(input2.resource), false);
assert.equal(observer.hasEditor(input3.resource), true);
assert.equal(observer.hasEditor(input4.resource), true);
assert.strictEqual(rootGroup.count, 3);
assert.strictEqual(rootGroup.isOpened(input1), true);
assert.strictEqual(rootGroup.isOpened(input2), false);
assert.strictEqual(rootGroup.isOpened(input3), true);
assert.strictEqual(rootGroup.isOpened(input4), true);
assert.strictEqual(observer.hasEditor(input1.resource), true);
assert.strictEqual(observer.hasEditor(input2.resource), false);
assert.strictEqual(observer.hasEditor(input3.resource), true);
assert.strictEqual(observer.hasEditor(input4.resource), true);
observer.dispose();
part.dispose();

View File

@@ -11,8 +11,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyEqualsExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -21,7 +21,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue';
import { timeout } from 'vs/base/common/async';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
// --- bisect service
@@ -74,6 +74,7 @@ class ExtensionBisectService implements IExtensionBisectService {
constructor(
@ILogService logService: ILogService,
@IStorageService private readonly _storageService: IStorageService,
@IWorkbenchEnvironmentService private readonly _envService: IWorkbenchEnvironmentService
) {
const raw = _storageService.get(ExtensionBisectService._storageKey, StorageScope.GLOBAL);
this._state = BisectState.fromJSON(raw);
@@ -98,12 +99,26 @@ class ExtensionBisectService implements IExtensionBisectService {
isDisabledByBisect(extension: IExtension): boolean {
if (!this._state) {
// bisect isn't active
return false;
}
if (this._isRemoteResolver(extension)) {
// the current remote resolver extension cannot be disabled
return false;
}
const disabled = this._disabled.get(extension.identifier.id);
return disabled ?? false;
}
private _isRemoteResolver(extension: IExtension): boolean {
if (extension.manifest.enableProposedApi !== true) {
return false;
}
const idx = this._envService.remoteAuthority?.indexOf('+');
const activationEvent = `onResolveRemoteAuthority:${this._envService.remoteAuthority?.substr(0, idx)}`;
return Boolean(extension.manifest.activationEvents?.find(e => e === activationEvent));
}
async start(extensions: ILocalExtension[]): Promise<void> {
if (this._state) {
throw new Error('invalid state');
@@ -198,10 +213,16 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'extension.bisect.start',
title: localize('title.start', "Start Extension Bisect"),
title: { value: localize('title.start', "Start Extension Bisect"), original: 'Start Extension Bisect' },
category: localize('help', "Help"),
f1: true,
precondition: ExtensionBisectUi.ctxIsBisectActive.negate()
precondition: ExtensionBisectUi.ctxIsBisectActive.negate(),
menu: {
id: MenuId.ViewContainerTitle,
when: ContextKeyEqualsExpr.create('viewContainer', 'workbench.view.extensions'),
group: '2_enablement',
order: 3
}
});
}
@@ -289,8 +310,7 @@ registerAction2(class extends Action2 {
await extensionEnablementService.disableExtension({ id: done.id }, undefined);
}
if (res.choice === 0) {
issueService.openReporter({ extensionId: done.id });
await timeout(750); // workaround for https://github.com/microsoft/vscode/issues/111871
await issueService.openReporter({ extensionId: done.id });
}
}
await bisectService.reset();

View File

@@ -79,10 +79,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
getEnablementState(extension: IExtension): EnablementState {
if (this.extensionBisectService.isDisabledByBisect(extension)) {
return EnablementState.DisabledByEnvironemt;
return EnablementState.DisabledByEnvironment;
}
if (this._isDisabledInEnv(extension)) {
return EnablementState.DisabledByEnvironemt;
return EnablementState.DisabledByEnvironment;
}
if (this._isDisabledByExtensionKind(extension)) {
return EnablementState.DisabledByExtensionKind;
@@ -97,7 +97,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return false;
}
const enablementState = this.getEnablementState(extension);
if (enablementState === EnablementState.DisabledByEnvironemt || enablementState === EnablementState.DisabledByExtensionKind) {
if (enablementState === EnablementState.DisabledByEnvironment || enablementState === EnablementState.DisabledByExtensionKind) {
return false;
}
return true;

View File

@@ -34,7 +34,7 @@ export interface IWorkbenchExtensioManagementService extends IExtensionManagemen
export const enum EnablementState {
DisabledByExtensionKind,
DisabledByEnvironemt,
DisabledByEnvironment,
DisabledGlobally,
DisabledWorkspace,
EnabledGlobally,

View File

@@ -266,7 +266,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
installOptions = { isMachineScoped, isBuiltin: false };
}
if (!installOptions.isMachineScoped) {
if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
@@ -314,32 +314,36 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return this.extensionManagementServerService.localExtensionManagementServer;
}
private isExtensionsSyncEnabled(): boolean {
return this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions);
}
private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise<boolean> {
if (!this.userDataAutoSyncEnablementService.isEnabled() || !this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions)) {
return false;
}
const result = await this.dialogService.show(
Severity.Info,
extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),
[
localize('install', "Install"),
localize('install and do no sync', "Install (Do not sync)"),
localize('cancel', "Cancel"),
],
{
cancelId: 2,
detail: extensions.length === 1
? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)
: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?")
if (this.isExtensionsSyncEnabled()) {
const result = await this.dialogService.show(
Severity.Info,
extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),
[
localize('install', "Install"),
localize('install and do no sync', "Install (Do not sync)"),
localize('cancel', "Cancel"),
],
{
cancelId: 2,
detail: extensions.length === 1
? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)
: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?")
}
);
switch (result.choice) {
case 0:
return false;
case 1:
return true;
}
);
switch (result.choice) {
case 0:
return false;
case 1:
return true;
throw canceled();
}
throw canceled();
return false;
}
getExtensionsReport(): Promise<IReportedExtension[]> {

View File

@@ -458,7 +458,7 @@ suite('ExtensionEnablementService Test', () => {
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(!testObject.isEnabled(extension));
assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironemt);
assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment);
});
test('test local workspace extension is disabled by kind', async () => {

View File

@@ -11,7 +11,7 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { FileKind, IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { isWorkspace, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -107,7 +107,7 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor
: await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to"));
for (const workspaceOrWorkspaceFolder of workspaceOrFolders) {
if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) {
if (isWorkspace(workspaceOrWorkspaceFolder)) {
await this.addOrRemoveWorkspaceRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isRecommended);
} else {
await this.addOrRemoveWorkspaceFolderRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isRecommended);
@@ -133,7 +133,7 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor
: await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to"));
for (const workspaceOrWorkspaceFolder of workspaceOrFolders) {
if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) {
if (isWorkspace(workspaceOrWorkspaceFolder)) {
await this.addOrRemoveWorkspaceUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isUnwanted);
} else {
await this.addOrRemoveWorkspaceFolderUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isUnwanted);

View File

@@ -84,10 +84,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten
protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void {
super._onExtensionHostCrashed(extensionHost, code, signal);
if (extensionHost.kind === ExtensionHostKind.LocalWebWorker) {
if (code === ExtensionHostExitCode.StartTimeout10s) {
if (code === ExtensionHostExitCode.StartTimeout60s) {
this._notificationService.prompt(
Severity.Error,
<<<<<<< HEAD
nls.localize('extensionService.startTimeout', 'The Web Worker Extension Host did not start in 10s.'),
=======
nls.localize('extensionService.startTimeout', "The Web Worker Extension Host did not start in 60s."),
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
[]
);
return;

View File

@@ -30,6 +30,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { Barrier } from 'vs/base/common/async';
import { FileAccess } from 'vs/base/common/network';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
@@ -40,6 +41,17 @@ export interface IWebWorkerExtensionHostDataProvider {
getInitData(): Promise<IWebWorkerExtensionHostInitData>;
}
const ttPolicy = window.trustedTypes?.createPolicy('webWorkerExtensionHost', { createScriptURL: value => value });
const ttPolicyNestedWorker = window.trustedTypes?.createPolicy('webNestedWorkerExtensionHost', {
createScriptURL(value) {
if (value.startsWith('blob:')) {
return value;
}
throw new Error(value + ' is NOT allowed');
}
});
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
public readonly kind = ExtensionHostKind.LocalWebWorker;
@@ -154,8 +166,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
};
startTimeout = setTimeout(() => {
rejectBarrier(ExtensionHostExitCode.StartTimeout10s, new Error('The Web Worker Extension Host did not start in 10s'));
}, 10000);
rejectBarrier(ExtensionHostExitCode.StartTimeout60s, new Error('The Web Worker Extension Host did not start in 60s'));
}, 60000);
this._register(dom.addDisposableListener(window, 'message', (event) => {
if (event.source !== iframe.contentWindow) {
@@ -217,20 +229,48 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(FileAccess.asBrowserUri('../worker/extensionHostWorkerMain.js', require).toString(true), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
const worker = new Worker(ttPolicy?.createScriptURL(url) as unknown as string ?? url, { name: 'WorkerExtensionHost' });
const barrier = new Barrier();
let port!: MessagePort;
const nestedWorker = new Map<string, Worker>();
worker.onmessage = (event) => {
const { data } = event;
if (barrier.isOpen() || !(data instanceof MessagePort)) {
const data: MessagePort | NewWorkerMessage | TerminateWorkerMessage = event.data;
if (data instanceof MessagePort) {
// receiving a message port which is used to communicate
// with the web worker extension host
if (barrier.isOpen()) {
console.warn('UNEXPECTED message', event);
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'received a message port AFTER opening the barrier']);
return;
}
port = data;
barrier.open();
} else if (data?.type === '_newWorker') {
// receiving a message to create a new nested/child worker
const worker = new Worker((ttPolicyNestedWorker?.createScriptURL(data.url) ?? data.url) as string, data.options);
worker.postMessage(data.port, [data.port]);
worker.onerror = console.error.bind(console);
nestedWorker.set(data.id, worker);
} else if (data?.type === '_terminateWorker') {
// receiving a message to terminate nested/child worker
if (nestedWorker.has(data.id)) {
nestedWorker.get(data.id)!.terminate();
nestedWorker.delete(data.id);
}
} else {
// all other messages are an error
console.warn('UNEXPECTED message', event);
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'UNEXPECTED message']);
return;
}
port = data;
barrier.open();
};
// await MessagePort and use it to directly communicate
@@ -325,7 +365,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
extensionTestsLocationURI: undefined, // never run extension tests in web worker extension host
globalStorageHome: this._environmentService.globalStorageHome,
workspaceStorageHome: this._environmentService.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,

View File

@@ -407,15 +407,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
//#endregion
protected async _initialize(): Promise<void> {
perf.mark('willLoadExtensions');
perf.mark('code/willLoadExtensions');
this._startExtensionHosts(true, []);
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
await this._scanAndHandleExtensions();
this._releaseBarrier();
perf.mark('code/didLoadExtensions');
}
private _releaseBarrier(): void {
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(undefined);
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
@@ -465,7 +464,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHosts();
if (extensionHost.kind === ExtensionHostKind.LocalProcess) {
this._stopExtensionHosts();
}
}
//#region IExtensionService
@@ -644,11 +645,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
const availableExtensions = this._registry.getAllExtensionDescriptions();
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
perf.mark('code/willHandleExtensionPoints');
for (const extensionPoint of extensionPoints) {
if (affectedExtensionPoints[extensionPoint.name]) {
AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler);
}
}
perf.mark('code/didHandleExtensionPoints');
}
private _handleExtensionPointMessage(msg: IMessage) {
@@ -699,9 +702,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
});
}
}
perf.mark(`willHandleExtensionPoint/${extensionPoint.name}`);
extensionPoint.acceptUsers(users);
perf.mark(`didHandleExtensionPoint/${extensionPoint.name}`);
}
private _showMessageToUser(severity: Severity, msg: string): void {

View File

@@ -5,6 +5,7 @@
import { timeout } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import * as performance from 'vs/base/common/performance';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
@@ -36,6 +37,7 @@ export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _hostUtils: IHostUtils;
private readonly _extensionService: IExtHostExtensionService;
private readonly _logService: ILogService;
private readonly _disposables = new DisposableStore();
constructor(
@@ -64,11 +66,11 @@ export class ExtensionHostMain {
const terminalService = instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService));
this._disposables.add(terminalService);
const logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
this._disposables.add(logService);
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
logService.info('extension host started');
logService.trace('initData', initData);
performance.mark(`code/extHost/didCreateServices`);
this._logService.info('extension host started');
this._logService.trace('initData', initData);
// ugly self - inject
// must call initialize *after* creating the extension service
@@ -110,12 +112,14 @@ export class ExtensionHostMain {
});
}
terminate(): void {
terminate(reason: string): void {
if (this._isTerminating) {
// we are already shutting down...
return;
}
this._isTerminating = true;
this._logService.info(`extension host terminating: ${reason}`);
this._logService.flush();
this._disposables.dispose();
@@ -127,7 +131,12 @@ export class ExtensionHostMain {
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
setTimeout(() => {
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => this._hostUtils.exit());
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => {
this._logService.info(`exiting with code 0`);
this._logService.flush();
this._logService.dispose();
this._hostUtils.exit(0);
});
}, 1000);
}

View File

@@ -182,6 +182,7 @@ export class ExtensionHostManager extends Disposable {
this._register(this._rpcProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
const extHostContext: IExtHostContext = {
remoteAuthority: this._extensionHost.remoteAuthority,
extensionHostKind: this.kind,
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._rpcProtocol!.getProxy(identifier),
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._rpcProtocol!.set(identifier, instance),
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._rpcProtocol!.assertRegistered(identifiers),
@@ -260,12 +261,12 @@ export class ExtensionHostManager extends Disposable {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
// This authority does not need to be resolved, simply parse the port number
const pieces = remoteAuthority.split(':');
const lastColon = remoteAuthority.lastIndexOf(':');
return Promise.resolve({
authority: {
authority: remoteAuthority,
host: pieces[0],
port: parseInt(pieces[1], 10),
host: remoteAuthority.substring(0, lastColon),
port: parseInt(remoteAuthority.substring(lastColon + 1), 10),
connectionToken: undefined
}
});

View File

@@ -8,7 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
export const enum ExtensionHostExitCode {
// nodejs uses codes 1-13 and exit codes >128 are signal exits
VersionMismatch = 55,
StartTimeout10s = 56,
StartTimeout60s = 56,
UnexpectedError = 81,
}
@@ -20,6 +20,8 @@ export interface IExtHostSocketMessage {
type: 'VSCODE_EXTHOST_IPC_SOCKET';
initialDataChunk: string;
skipWebSocketFrames: boolean;
permessageDeflate: boolean;
inflateBytes: string;
}
export interface IExtHostReduceGraceTimeMessage {

View File

@@ -305,6 +305,11 @@ export const schema: IJSONSchema = {
body: 'onCustomEditor:${9:viewType}',
description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'),
},
{
label: 'onNotebook',
body: 'onNotebook:${10:viewType}',
description: nls.localize('vscode.extension.activationEvents.onNotebook', 'An activation event emitted whenever the specified notebook document is opened.'),
},
{
label: '*',
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
export interface NewWorkerMessage {
type: '_newWorker';
id: string;
port: any /* MessagePort */;
url: string;
options: any /* WorkerOptions */ | undefined;
}
export interface TerminateWorkerMessage {
type: '_terminateWorker';
id: string;
}

View File

@@ -57,6 +57,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
public readonly onExit: Event<[number, string | null]> = this._onExit.event;
private _protocol: PersistentProtocol | null;
private _hasLostConnection: boolean;
private _terminating: boolean;
private readonly _isExtensionDevHost: boolean;
@@ -77,6 +78,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
super();
this.remoteAuthority = this._initDataProvider.remoteAuthority;
this._protocol = null;
this._hasLostConnection = false;
this._terminating = false;
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
@@ -130,7 +132,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
}
protocol.onClose(() => {
protocol.onDidDispose(() => {
this._onExtHostConnectionLost();
});
@@ -188,6 +190,11 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
}
private _onExtHostConnectionLost(): void {
if (this._hasLostConnection) {
// avoid re-entering this method
return;
}
this._hasLostConnection = true;
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {
this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);

View File

@@ -246,6 +246,24 @@ export class ExtensionService extends AbstractExtensionService implements IExten
signal,
extensionIds: activatedExtensions.map(e => e.value)
});
for (const extensionId of activatedExtensions) {
type ExtensionHostCrashExtensionClassification = {
code: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
signal: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
type ExtensionHostCrashExtensionEvent = {
code: number;
signal: string | null;
extensionId: string;
};
this._telemetryService.publicLog2<ExtensionHostCrashExtensionEvent, ExtensionHostCrashExtensionClassification>('extensionHostCrashExtension', {
code,
signal,
extensionId: extensionId.value
});
}
}
}

View File

@@ -45,6 +45,8 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { isUUID } from 'vs/base/common/uuid';
import { join } from 'vs/base/common/path';
import { Readable, Writable } from 'stream';
import { StringDecoder } from 'string_decoder';
export interface ILocalProcessExtensionHostInitData {
readonly autoStart: boolean;
@@ -55,6 +57,11 @@ export interface ILocalProcessExtensionHostDataProvider {
getInitData(): Promise<ILocalProcessExtensionHostInitData>;
}
const enum NativeLogMarkers {
Start = 'START_NATIVE_LOG',
End = 'END_NATIVE_LOG',
}
export class LocalProcessExtensionHost implements IExtensionHost {
public readonly kind = ExtensionHostKind.LocalProcess;
@@ -151,13 +158,12 @@ export class LocalProcessExtensionHost implements IExtensionHost {
this._messageProtocol = Promise.all([
this._tryListenOnPipe(),
this._tryFindDebugPort()
]).then(data => {
const pipeName = data[0];
const portNumber = data[1];
]).then(([pipeName, portNumber]) => {
const env = objects.mixin(objects.deepClone(process.env), {
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: true,
VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
VSCODE_PIPE_LOGGING: 'true',
VSCODE_VERBOSE_LOGGING: true,
VSCODE_LOG_NATIVE: this._isExtensionDevHost,
VSCODE_IPC_HOOK_EXTHOST: pipeName,
VSCODE_HANDLES_UNCAUGHT_ERRORS: true,
VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose),
@@ -196,6 +202,10 @@ export class LocalProcessExtensionHost implements IExtensionHost {
opts.execArgv = ['--inspect-port=0'];
}
if (this._environmentService.args['prof-v8-extensions']) {
opts.execArgv.unshift('--prof');
}
// On linux crash reporter needs to be started on child node processes explicitly
if (platform.isLinux) {
const crashReporterStartOptions: CrashReporterStartOptions = {
@@ -217,7 +227,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// For https://github.com/microsoft/vscode/issues/105743
const extHostCrashDirectory = this._environmentService.crashReporterDirectory || this._environmentService.userDataPath;
opts.env.BREAKPAD_DUMP_LOCATION = join(extHostCrashDirectory, `${ExtensionHostLogFileName} Crash Reports`);
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
opts.env.VSCODE_CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
}
// Run Extension Host as fork of current process
@@ -225,13 +235,11 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// Catch all output coming from the extension host process
type Output = { data: string, format: string[] };
this._extensionHostProcess.stdout!.setEncoding('utf8');
this._extensionHostProcess.stderr!.setEncoding('utf8');
const onStdout = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stdout!, 'data');
const onStderr = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stderr!, 'data');
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.stdout!);
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.stderr!);
const onOutput = Event.any(
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
@@ -497,11 +505,6 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// Send to local console
log(entry, 'Extension Host');
// Broadcast to other windows if we are in development mode
if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) {
this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry);
}
}
}
@@ -525,6 +528,44 @@ export class LocalProcessExtensionHost implements IExtensionHost {
this._onExit.fire([code, signal]);
}
private _handleProcessOutputStream(stream: Readable) {
let last = '';
let isOmitting = false;
const event = new Emitter<string>();
const decoder = new StringDecoder('utf-8');
stream.pipe(new Writable({
write(chunk, _encoding, callback) {
// not a fancy approach, but this is the same approach used by the split2
// module which is well-optimized (https://github.com/mcollina/split2)
last += typeof chunk === 'string' ? chunk : decoder.write(chunk);
let lines = last.split(/\r?\n/g);
last = lines.pop()!;
// protected against an extension spamming and leaking memory if no new line is written.
if (last.length > 10_000) {
lines.push(last);
last = '';
}
for (const line of lines) {
if (isOmitting) {
if (line === NativeLogMarkers.End) {
isOmitting = false;
}
} else if (line === NativeLogMarkers.Start) {
isOmitting = true;
} else if (line.length) {
event.fire(line + '\n');
}
}
callback();
}
}));
return event;
}
public async enableInspectPort(): Promise<boolean> {
if (typeof this._inspectPort === 'number') {
return true;
@@ -614,7 +655,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// to communicate this back to the main side to terminate the debug session
if (this._isExtensionDevHost && !this._isExtensionDevTestFromCli && !this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
this._extensionHostDebugService.terminateSession(this._environmentService.debugExtensionHost.debugId);
event.join(timeout(100 /* wait a bit for IPC to get delivered */));
event.join(timeout(100 /* wait a bit for IPC to get delivered */), 'join.extensionDevelopment');
}
}
}

View File

@@ -6,6 +6,7 @@
import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net';
import * as minimist from 'minimist';
import * as performance from 'vs/base/common/performance';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
@@ -93,7 +94,7 @@ interface IRendererConnection {
// This calls exit directly in case the initialization is not finished and we need to exit
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
let onTerminate = function () {
let onTerminate = function (reason: string) {
nativeExit();
};
@@ -110,8 +111,8 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime;
const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime;
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate(), reconnectionGraceTime);
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate(), reconnectionShortGraceTime);
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (1)'), reconnectionGraceTime);
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (2)'), reconnectionShortGraceTime);
process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
@@ -120,7 +121,8 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
if (msg.skipWebSocketFrames) {
socket = new NodeSocket(handle);
} else {
socket = new WebSocketNodeSocket(new NodeSocket(handle));
const inflateBytes = VSBuffer.wrap(Buffer.from(msg.inflateBytes, 'base64'));
socket = new WebSocketNodeSocket(new NodeSocket(handle), msg.permessageDeflate, inflateBytes, false);
}
if (protocol) {
// reconnection case
@@ -131,7 +133,7 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
} else {
clearTimeout(timer);
protocol = new PersistentProtocol(socket, initialDataChunk);
protocol.onClose(() => onTerminate());
protocol.onDidDispose(() => onTerminate('renderer disconnected'));
resolve(protocol);
// Wait for rich client to reconnect
@@ -195,7 +197,7 @@ async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
protocol.onMessage((msg) => {
if (isMessageOfType(msg, MessageType.Terminate)) {
this._terminating = true;
onTerminate();
onTerminate('received terminate message from renderer');
} else {
this._onMessage.fire(msg);
}
@@ -267,11 +269,23 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
});
// Kill oneself if one's parent dies. Much drama.
let epermErrors = 0;
setInterval(function () {
try {
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
epermErrors = 0;
} catch (e) {
onTerminate();
if (e && e.code === 'EPERM') {
// Even if the parent process is still alive,
// some antivirus software can lead to an EPERM error to be thrown here.
// Let's terminate only if we get 3 consecutive EPERM errors.
epermErrors++;
if (epermErrors >= 3) {
onTerminate(`parent process ${initData.parentPid} does not exist anymore (3 x EPERM): ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
}
} else {
onTerminate(`parent process ${initData.parentPid} does not exist anymore: ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
}
}
}, 1000);
@@ -299,10 +313,16 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
}
export async function startExtensionHostProcess(): Promise<void> {
<<<<<<< HEAD
proxyAgent.monkeyPatch(true);
=======
performance.mark(`code/extHost/willConnectToRenderer`);
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
const protocol = await createExtHostProtocol();
performance.mark(`code/extHost/didConnectToRenderer`);
const renderer = await connectToRenderer(protocol);
performance.mark(`code/extHost/didWaitForInitData`);
const { initData } = renderer;
// setup things
patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/microsoft/vscode/issues/37708)
@@ -334,5 +354,5 @@ export async function startExtensionHostProcess(): Promise<void> {
);
// rewrite onTerminate-function to be a proper shutdown
onTerminate = () => extensionHostMain.terminate();
onTerminate = (reason: string) => extensionHostMain.terminate(reason);
}

View File

@@ -12,7 +12,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/common/extensionValidator';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
@@ -47,10 +47,19 @@ abstract class ExtensionManifestHandler {
class ExtensionManifestParser extends ExtensionManifestHandler {
private static _fastParseJSON(text: string, errors: json.ParseError[]): any {
try {
return JSON.parse(text);
} catch (err) {
// invalid JSON, let's get good errors
return json.parse(text, errors);
}
}
public parse(): Promise<IExtensionDescription> {
return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => {
const errors: json.ParseError[] = [];
const manifest = json.parse(manifestContents.toString(), errors);
const manifest = ExtensionManifestParser._fastParseJSON(manifestContents.toString(), errors);
if (json.getNodeType(manifest) !== 'object') {
this._log.error(this._absoluteFolderPath, nls.localize('jsonParseInvalidType', "Invalid manifest file {0}: Not an JSON object.", this._absoluteManifestPath));
} else if (errors.length === 0) {
@@ -326,7 +335,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
}
// id := `publisher.name`
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
extensionDescription.id = getExtensionId(extensionDescription.publisher, extensionDescription.name);
extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id);
extensionDescription.extensionLocation = URI.file(this._absoluteFolderPath);

View File

@@ -397,7 +397,7 @@ function tlsPatches(originals: typeof tls) {
};
function patch(original: typeof tls.createSecureContext): typeof tls.createSecureContext {
return function (details: tls.SecureContextOptions): ReturnType<typeof tls.createSecureContext> {
return function (details?: tls.SecureContextOptions): ReturnType<typeof tls.createSecureContext> {
const context = original.apply(null, arguments as any);
const certs = (details as any)._vscodeAdditionalCaCerts;
if (certs) {
@@ -498,7 +498,7 @@ async function readWindowsCaCertificates() {
const winCA = await import('vscode-windows-ca-certs');
let ders: any[] = [];
const store = winCA();
const store = new winCA.Crypt32();
try {
let der: any;
while (der = store.next()) {

View File

@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* 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 { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService';
import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService';
suite('BrowserExtensionService', () => {
test('pickRunningLocation', () => {
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true), ExtensionRunningLocation.Remote);
});
});

View File

@@ -10,7 +10,7 @@ import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common
suite('ExtensionKind', () => {
function check(manifest: Partial<IExtensionManifest>, expected: ExtensionKind[]): void {
assert.deepEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
assert.deepStrictEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
}
test('declarative with extension dependencies => workspace', () => {

View File

@@ -56,7 +56,7 @@ suite('RPCProtocol', () => {
test('simple call', function (done) {
delegate = (a1: number, a2: number) => a1 + a2;
bProxy.$m(4, 1).then((res: number) => {
assert.equal(res, 5);
assert.strictEqual(res, 5);
done(null);
}, done);
});
@@ -64,7 +64,7 @@ suite('RPCProtocol', () => {
test('simple call without result', function (done) {
delegate = (a1: number, a2: number) => { };
bProxy.$m(4, 1).then((res: number) => {
assert.equal(res, undefined);
assert.strictEqual(res, undefined);
done(null);
}, done);
});
@@ -80,7 +80,7 @@ suite('RPCProtocol', () => {
b.buffer[2] = 3;
b.buffer[3] = 4;
bProxy.$m(b, 2).then((res: number) => {
assert.equal(res, 3);
assert.strictEqual(res, 3);
done(null);
}, done);
});
@@ -96,10 +96,10 @@ suite('RPCProtocol', () => {
};
bProxy.$m(4, 1).then((res: VSBuffer) => {
assert.ok(res instanceof VSBuffer);
assert.equal(res.buffer[0], 1);
assert.equal(res.buffer[1], 2);
assert.equal(res.buffer[2], 3);
assert.equal(res.buffer[3], 4);
assert.strictEqual(res.buffer[0], 1);
assert.strictEqual(res.buffer[1], 2);
assert.strictEqual(res.buffer[2], 3);
assert.strictEqual(res.buffer[3], 4);
done(null);
}, done);
});
@@ -121,7 +121,7 @@ suite('RPCProtocol', () => {
return a1 + 1;
};
bProxy.$m(4, CancellationToken.None).then((res: number) => {
assert.equal(res, 5);
assert.strictEqual(res, 5);
done(null);
}, done);
});
@@ -138,7 +138,7 @@ suite('RPCProtocol', () => {
let tokenSource = new CancellationTokenSource();
let p = bProxy.$m(4, tokenSource.token);
p.then((res: number) => {
assert.equal(res, 7);
assert.strictEqual(res, 7);
}, (err) => {
assert.fail('should not receive error');
}).finally(done);
@@ -152,7 +152,7 @@ suite('RPCProtocol', () => {
bProxy.$m(4, 1).then((res) => {
assert.fail('unexpected');
}, (err) => {
assert.equal(err.message, 'nope');
assert.strictEqual(err.message, 'nope');
}).finally(done);
});
@@ -163,7 +163,7 @@ suite('RPCProtocol', () => {
bProxy.$m(4, 1).then((res) => {
assert.fail('unexpected');
}, (err) => {
assert.equal(err, undefined);
assert.strictEqual(err, undefined);
}).finally(done);
});
@@ -174,7 +174,7 @@ suite('RPCProtocol', () => {
return circular;
};
bProxy.$m(4, 1).then((res) => {
assert.equal(res, null);
assert.strictEqual(res, null);
}, (err) => {
assert.fail('unexpected');
}).finally(done);
@@ -188,18 +188,18 @@ suite('RPCProtocol', () => {
bProxy.$m(4, 1).then((res) => {
assert.fail('unexpected');
}, (err) => {
assert.equal(err.what, 'what');
assert.strictEqual(err.what, 'what');
}).finally(done);
});
test('undefined arguments arrive as null', function () {
delegate = (a1: any, a2: any) => {
assert.equal(typeof a1, 'undefined');
assert.equal(a2, null);
assert.strictEqual(typeof a1, 'undefined');
assert.strictEqual(a2, null);
return 7;
};
return bProxy.$m(undefined, null).then((res) => {
assert.equal(res, 7);
assert.strictEqual(res, 7);
});
});

View File

@@ -1,88 +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 * as assert from 'assert';
import { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService';
import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService';
suite('BrowserExtensionService', () => {
test('pickRunningLocation', () => {
assert.deepEqual(BrowserExtensionService.pickRunningLocation([], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation([], false, true), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation([], true, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation([], true, true), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false), ExtensionRunningLocation.None);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true), ExtensionRunningLocation.Remote);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
assert.deepEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true), ExtensionRunningLocation.Remote);
});
});

View File

@@ -10,7 +10,9 @@ import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { NestedWorker } from 'vs/workbench/services/extensions/worker/polyfillNestedWorker';
import * as path from 'vs/base/common/path';
import * as performance from 'vs/base/common/performance';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/worker/extHost.worker.services';
@@ -23,6 +25,8 @@ declare namespace self {
let close: any;
let postMessage: any;
let addEventListener: any;
let removeEventListener: any;
let dispatchEvent: any;
let indexedDB: { open: any, [k: string]: any };
let caches: { open: any, [k: string]: any };
}
@@ -46,13 +50,24 @@ self.addEventListener = () => console.trace(`'addEventListener' has been blocked
(<any>self)['webkitResolveLocalFileSystemURL'] = undefined;
if ((<any>self).Worker) {
// make sure new Worker(...) always uses data:
const ttPolicy = (<any>self).trustedTypes?.createPolicy('extensionHostWorker', { createScriptURL: (value: string) => value });
// make sure new Worker(...) always uses blob: (to maintain current origin)
const _Worker = (<any>self).Worker;
Worker = <any>function (stringUrl: string | URL, options?: WorkerOptions) {
const js = `importScripts('${stringUrl}');`;
options = options || {};
options.name = options.name || path.basename(stringUrl.toString());
return new _Worker(`data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`, options);
const blob = new Blob([js], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
return new _Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) : blobUrl, options);
};
} else {
(<any>self).Worker = class extends NestedWorker {
constructor(stringOrUrl: string | URL, options?: WorkerOptions) {
super(nativePostMessage, stringOrUrl, { name: path.basename(stringOrUrl.toString()), ...options });
}
};
}
@@ -97,7 +112,7 @@ class ExtensionWorker {
if (isMessageOfType(msg, MessageType.Terminate)) {
// handle terminate-message right here
terminating = true;
onTerminate();
onTerminate('received terminate message from renderer');
return;
}
@@ -133,13 +148,13 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
});
}
let onTerminate = nativeClose;
let onTerminate = (reason: string) => nativeClose();
(function create(): void {
const res = new ExtensionWorker();
performance.mark(`code/extHost/willConnectToRenderer`);
connectToRenderer(res.protocol).then(data => {
performance.mark(`code/extHost/didWaitForInitData`);
const extHostMain = new ExtensionHostMain(
data.protocol,
data.initData,
@@ -147,6 +162,6 @@ let onTerminate = nativeClose;
null,
);
onTerminate = () => extHostMain.terminate();
onTerminate = (reason: string) => extHostMain.terminate(reason);
});
})();

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data:; script-src 'unsafe-eval' 'sha256-DhNBVT9y4y9LG937ZrEbN5CwALd+WSpQnG3z5u1MOFk=' http: https:; connect-src http: https:" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-LU+tuagpyx5mKuYgHsSvz9593ZGS6yeLPRvzq1lKXlY=' http: https:; connect-src http: https: ws: wss:" />
</head>
<body>
<script>
@@ -22,13 +22,31 @@
try {
const worker = new Worker('extensionHostWorkerMain.js', { name: 'WorkerExtensionHost' });
const nestedWorkers = new Map();
worker.onmessage = (event) => {
const { data } = event;
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
if (data?.type === '_newWorker') {
const { id, port, url, options } = data;
const newWorker = new Worker(url, options);
newWorker.postMessage(port, [port]);
worker.onerror = console.error.bind(console);
nestedWorkers.set(id, newWorker);
} else if (data?.type === '_terminateWorker') {
const { id } = data;
if(nestedWorkers.has(id)) {
nestedWorkers.get(id).terminate();
nestedWorkers.delete(id);
}
} else {
worker.onerror = console.error.bind(console);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
}
};
worker.onerror = (event) => {

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data:; script-src 'unsafe-eval' 'sha256-DhNBVT9y4y9LG937ZrEbN5CwALd+WSpQnG3z5u1MOFk=' https:; connect-src https:" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-LU+tuagpyx5mKuYgHsSvz9593ZGS6yeLPRvzq1lKXlY=' https:; connect-src https: wss:" />
</head>
<body>
<script>
@@ -22,13 +22,31 @@
try {
const worker = new Worker('extensionHostWorkerMain.js', { name: 'WorkerExtensionHost' });
const nestedWorkers = new Map();
worker.onmessage = (event) => {
const { data } = event;
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
if (data?.type === '_newWorker') {
const { id, port, url, options } = data;
const newWorker = new Worker(url, options);
newWorker.postMessage(port, [port]);
worker.onerror = console.error.bind(console);
nestedWorkers.set(id, newWorker);
} else if (data?.type === '_terminateWorker') {
const { id } = data;
if(nestedWorkers.has(id)) {
nestedWorkers.get(id).terminate();
nestedWorkers.delete(id);
}
} else {
worker.onerror = console.error.bind(console);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
}
};
worker.onerror = (event) => {

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
declare function postMessage(data: any, transferables?: Transferable[]): void;
declare type MessageEventHandler = ((ev: MessageEvent<any>) => any) | null;
const _bootstrapFnSource = (function _bootstrapFn(workerUrl: string) {
const listener: EventListener = (event: Event): void => {
// uninstall handler
self.removeEventListener('message', listener);
// get data
const port = <MessagePort>(<MessageEvent>event).data;
// postMessage
// onmessage
Object.defineProperties(self, {
'postMessage': {
value(data: any, transferOrOptions?: any) {
port.postMessage(data, transferOrOptions);
}
},
'onmessage': {
get() {
return port.onmessage;
},
set(value: MessageEventHandler) {
port.onmessage = value;
}
}
// todo onerror
});
port.addEventListener('message', msg => {
self.dispatchEvent(new MessageEvent('message', { data: msg.data }));
});
port.start();
// fake recursively nested worker
self.Worker = <any>class { constructor() { throw new TypeError('Nested workers from within nested worker are NOT supported.'); } };
// load module
importScripts(workerUrl);
};
self.addEventListener('message', listener);
}).toString();
export class NestedWorker extends EventTarget implements Worker {
onmessage: ((this: Worker, ev: MessageEvent<any>) => any) | null = null;
onmessageerror: ((this: Worker, ev: MessageEvent<any>) => any) | null = null;
onerror: ((this: AbstractWorker, ev: ErrorEvent) => any) | null = null;
readonly terminate: () => void;
readonly postMessage: (message: any, options?: any) => void;
constructor(nativePostMessage: typeof postMessage, stringOrUrl: string | URL, options?: WorkerOptions) {
super();
// create bootstrap script
const bootstrap = `((${_bootstrapFnSource})('${stringOrUrl}'))`;
const blob = new Blob([bootstrap], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
const channel = new MessageChannel();
const id = blobUrl; // works because blob url is unique, needs ID pool otherwise
const msg: NewWorkerMessage = {
type: '_newWorker',
id,
port: channel.port2,
url: blobUrl,
options,
};
nativePostMessage(msg, [channel.port2]);
// worker-impl: functions
this.postMessage = channel.port1.postMessage.bind(channel.port1);
this.terminate = () => {
const msg: TerminateWorkerMessage = {
type: '_terminateWorker',
id
};
channel.port1.postMessage(msg);
URL.revokeObjectURL(blobUrl);
channel.port1.close();
channel.port2.close();
};
// worker-impl: events
Object.defineProperties(this, {
'onmessage': {
get() {
return channel.port1.onmessage;
},
set(value: MessageEventHandler) {
channel.port1.onmessage = value;
}
},
'onmessageerror': {
get() {
return channel.port1.onmessageerror;
},
set(value: MessageEventHandler) {
channel.port1.onmessageerror = value;
}
},
// todo onerror
});
channel.port1.addEventListener('messageerror', evt => {
const msgEvent = new MessageEvent('messageerror', { data: evt.data });
this.dispatchEvent(msgEvent);
});
channel.port1.addEventListener('message', evt => {
const msgEvent = new MessageEvent('message', { data: evt.data });
this.dispatchEvent(msgEvent);
});
channel.port1.start();
}
}

View File

@@ -4,12 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
const setupIcon = registerIcon('getting-started-setup', Codicon.heart, localize('getting-started-setup-icon', "Icon used for the setup category of getting started"));
const beginnerIcon = registerIcon('getting-started-beginner', Codicon.lightbulb, localize('getting-started-beginner-icon', "Icon used for the beginner category of getting started"));
const codespacesIcon = registerIcon('getting-started-codespaces', Codicon.github, localize('getting-started-codespaces-icon', "Icon used for the codespaces category of getting started"));
type GettingStartedItem = {
id: string
title: string,
description: string,
button: { title: string, command: string },
button:
| { title: string, command?: never, link: string }
| { title: string, command: string, link?: never },
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, }
when?: string,
media: { type: 'image', path: string, altText: string },
@@ -19,7 +30,7 @@ type GettingStartedCategory = {
id: string
title: string,
description: string,
codicon: string,
icon: ThemeIcon,
when?: string,
content:
| { type: 'items', items: GettingStartedItem[] }
@@ -30,141 +41,208 @@ type GettingStartedContent = GettingStartedCategory[];
export const content: GettingStartedContent = [
{
id: 'Beginner',
title: localize('gettingStarted.beginner.title', "Get Started"),
codicon: 'lightbulb',
description: localize('gettingStarted.beginner.description', "Get to know your new editor"),
id: 'Codespaces',
title: localize('gettingStarted.codespaces.title', "Primer on Codespaces"),
icon: codespacesIcon,
when: 'remoteName == codespaces',
description: localize('gettingStarted.codespaces.description', "Get up and running with your instant code environment."),
content: {
type: 'items',
items: [
{
id: 'runProjectTask',
title: localize('gettingStarted.runProject.title', "Build & run your app"),
description: localize('gettingStarted.runProject.description', "Build, run & debug your code in the cloud, right from the browser."),
button: {
title: localize('gettingStarted.runProject.button', "Start Debugging (F5)"),
command: 'workbench.action.debug.selectandstart'
},
doneOn: { commandExecuted: 'workbench.action.debug.selectandstart' },
media: { type: 'image', altText: 'Node.js project running debug mode and paused.', path: 'runProject.png' },
},
{
id: 'forwardPortsTask',
title: localize('gettingStarted.forwardPorts.title', "Access your running application"),
description: localize('gettingStarted.forwardPorts.description', "Ports running within your codespace are automatically forwarded to the web, so you can open them in your browser."),
button: {
title: localize('gettingStarted.forwardPorts.button', "Show Ports Panel"),
command: '~remote.forwardedPorts.focus'
},
doneOn: { commandExecuted: '~remote.forwardedPorts.focus' },
media: { type: 'image', altText: 'Ports panel.', path: 'forwardPorts.png' },
},
{
id: 'pullRequests',
title: localize('gettingStarted.pullRequests.title', "Pull requests at your fingertips"),
description: localize('gettingStarted.pullRequests.description', "Bring your GitHub workflow closer to your code, so you can review pull requests, add comments, merge branches, and more."),
button: {
title: localize('gettingStarted.pullRequests.button', "Open GitHub View"),
command: 'workbench.view.extension.github-pull-requests'
},
doneOn: { commandExecuted: 'workbench.view.extension.github-pull-requests' },
media: { type: 'image', altText: 'Preview for reviewing a pull request.', path: 'pullRequests.png' },
},
{
id: 'remoteTerminal',
title: localize('gettingStarted.remoteTerminal.title', "Run tasks in the integrated terminal"),
description: localize('gettingStarted.remoteTerminal.description', "Perform quick command-line tasks using the built-in terminal."),
button: {
title: localize('gettingStarted.remoteTerminal.button', "Focus Terminal"),
command: 'terminal.focus'
},
doneOn: { commandExecuted: 'terminal.focus' },
media: { type: 'image', altText: 'Remote terminal showing npm commands.', path: 'remoteTerminal.png' },
},
{
id: 'openVSC',
title: localize('gettingStarted.openVSC.title', "Develop remotely in VS Code"),
description: localize('gettingStarted.openVSC.description', "Access the power of your cloud development environment from your local VS Code. Set it up by installing the GitHub Codespaces extension and connecting your GitHub account."),
button: {
title: localize('gettingStarted.openVSC.button', "Open in VS Code"),
command: 'github.codespaces.openInStable'
},
when: 'isWeb',
doneOn: { commandExecuted: 'github.codespaces.openInStable' },
media: { type: 'image', altText: 'Preview of the Open in VS Code command.', path: 'openVSC.png' },
}
]
}
},
{
id: 'Setup',
title: localize('gettingStarted.setup.title', "Quick Setup"),
description: localize('gettingStarted.setup.description', "Extend and customize VS Code to make it yours."),
icon: setupIcon,
when: 'remoteName != codespaces',
content: {
type: 'items',
items: [
{
id: 'pickColorTheme',
description: localize('pickColorTask.description', "Modify the colors in the user interface to suit your preferences and work environment."),
title: localize('pickColorTask.title', "Color Theme"),
button: { title: localize('pickColorTask.button', "Find a Theme"), command: 'workbench.action.selectTheme' },
title: localize('gettingStarted.pickColor.title', "Customize the look with themes"),
description: localize('gettingStarted.pickColor.description', "Pick a color theme to match your taste and mood while coding."),
button: { title: localize('gettingStarted.pickColor.button', "Pick a Theme"), command: 'workbench.action.selectTheme' },
doneOn: { eventFired: 'themeSelected' },
media: { type: 'image', altText: 'ColorTheme', path: 'ColorTheme.jpg', }
media: { type: 'image', altText: 'Color theme preview for dark and light theme.', path: 'colorTheme.png', }
},
{
id: 'findKeybindingsExtensions',
description: localize('findKeybindingsTask.description', "Find keyboard shortcuts for Vim, Sublime, Atom and others."),
title: localize('findKeybindingsTask.title', "Configure Keybindings"),
button: {
title: localize('findKeybindingsTask.button', "Search for Keymaps"),
command: 'workbench.extensions.action.showRecommendedKeymapExtensions'
},
doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedKeymapExtensions' },
media: { type: 'image', altText: 'Extensions', path: 'Extensions.jpg', }
},
{
id: 'findLanguageExtensions',
description: localize('findLanguageExtsTask.description', "Get support for your languages like JavaScript, Python, Java, Azure, Docker, and more."),
title: localize('findLanguageExtsTask.title', "Languages & Tools"),
title: localize('gettingStarted.findLanguageExts.title', "Code in any language, without switching editors"),
description: localize('gettingStarted.findLanguageExts.description', "VS Code supports over 50+ programming languages. While many are built-in, others can be easily installed as extensions in one click."),
button: {
title: localize('findLanguageExtsTask.button', "Install Language Support"),
title: localize('gettingStarted.findLanguageExts.button', "Browse Language Extensions"),
command: 'workbench.extensions.action.showLanguageExtensions',
},
doneOn: { commandExecuted: 'workbench.extensions.action.showLanguageExtensions' },
media: { type: 'image', altText: 'Languages', path: 'Languages.jpg', }
media: { type: 'image', altText: 'Language extensions', path: 'languageExtensions.png', }
},
{
id: 'settingsSync',
title: localize('gettingStarted.settingsSync.title', "Sync your favorite setup"),
description: localize('gettingStarted.settingsSync.description', "Never lose the perfect VS Code setup! Settings Sync will back up and share settings, keybindings & extensions across several VS Code instances."),
when: 'syncStatus != uninitialized',
button: {
title: localize('gettingStarted.settingsSync.button', "Enable Settings Sync"),
command: 'workbench.userDataSync.actions.turnOn',
},
doneOn: { eventFired: 'sync-enabled' },
media: { type: 'image', altText: 'The "Turn on Sync" entry in the settings gear menu.', path: 'settingsSync.png', }
},
{
id: 'pickAFolderTask-Mac',
description: localize('gettingStartedOpenFolder.description', "Open a project folder to get started!"),
title: localize('gettingStartedOpenFolder.title', "Open Folder"),
title: localize('gettingStarted.setup.OpenFolder.title', "Open your project"),
description: localize('gettingStarted.setup.OpenFolder.description', "Open a project folder to get started!"),
when: 'isMac',
button: {
title: localize('gettingStartedOpenFolder.button', "Pick a Folder"),
title: localize('gettingStarted.setup.OpenFolder.button', "Pick a Folder"),
command: 'workbench.action.files.openFileFolder'
},
doneOn: { commandExecuted: 'workbench.action.files.openFileFolder' },
media: { type: 'image', altText: 'OpenFolder', path: 'OpenFolder.jpg' }
media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.png' }
},
{
id: 'pickAFolderTask-Other',
description: localize('gettingStartedOpenFolder.description', "Open a project folder to get started!"),
title: localize('gettingStartedOpenFolder.title', "Open Folder"),
title: localize('gettingStarted.setup.OpenFolder.title', "Open your project"),
description: localize('gettingStarted.setup.OpenFolder.description2', "Open a folder to get started!"),
when: '!isMac',
button: {
title: localize('gettingStartedOpenFolder.button', "Pick a Folder"),
title: localize('gettingStarted.setup.OpenFolder.button', "Pick a Folder"),
command: 'workbench.action.files.openFolder'
},
doneOn: { commandExecuted: 'workbench.action.files.openFolder' },
media: { type: 'image', altText: 'OpenFolder', path: 'OpenFolder.jpg' }
media: { type: 'image', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.png' }
}
]
}
},
{
id: 'Intermediate',
title: localize('gettingStarted.intermediate.title', "Essentials"),
codicon: 'heart',
description: localize('gettingStarted.intermediate.description', "Must know features you'll love"),
id: 'Beginner',
title: localize('gettingStarted.beginner.title', "Learn the Fundamentals"),
icon: beginnerIcon,
description: localize('gettingStarted.beginner.description', "Save time with these must-have shortcuts & features."),
content: {
type: 'items',
items: [
{
id: 'commandPaletteTask',
description: localize('commandPaletteTask.description', "The easiest way to find everything VS Code can do. If you\'re ever looking for a feature, check here first!"),
title: localize('commandPaletteTask.title', "Command Palette"),
title: localize('gettingStarted.commandPalette.title', "Find & run commands"),
description: localize('gettingStarted.commandPalette.description', "The easiest way to find everything VS Code can do. If you're ever looking for a feature or a shortcut, check here first!"),
button: {
title: localize('commandPaletteTask.button', "View All Commands"),
title: localize('gettingStarted.commandPalette.button', "Open Command Palette"),
command: 'workbench.action.showCommands'
},
doneOn: { commandExecuted: 'workbench.action.showCommands' },
media: { type: 'image', altText: 'gif of a custom tree hover', path: 'https://code.visualstudio.com/assets/updates/1_51/custom-tree-hover.gif' },
media: { type: 'image', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.png' },
},
{
id: 'terminal',
title: localize('gettingStarted.terminal.title', "Run tasks in the integrated terminal"),
description: localize('gettingStarted.terminal.description', "Quickly run shell commands and monitor build output, right next to your code."),
when: 'remoteName != codespaces',
button: {
title: localize('gettingStarted.terminal.button', "Open Terminal"),
command: 'workbench.action.terminal.toggleTerminal'
},
doneOn: { commandExecuted: 'workbench.action.terminal.toggleTerminal' },
media: { type: 'image', altText: 'Integrated terminal running a few npm commands', path: 'terminal.png' },
},
{
id: 'extensions',
title: localize('gettingStarted.extensions.title', "Limitless extensibility"),
description: localize('gettingStarted.extensions.description', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities."),
button: {
title: localize('gettingStarted.extensions.button', "Browse Recommended Extensions"),
command: 'workbench.extensions.action.showRecommendedExtensions'
},
doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedExtensions' },
media: { type: 'image', altText: 'VS Code extension marketplace with featured language extensions', path: 'extensions.png' },
},
{
id: 'settings',
title: localize('gettingStarted.settings.title', "Everything is a setting"),
description: localize('gettingStarted.settings.description', "Optimize every part of VS Code's look & feel to your liking. Enabling Settings Sync lets you share your personal tweaks across machines."),
button: {
title: localize('gettingStarted.settings.button', "Tweak my Settings"),
command: 'workbench.action.openSettings'
},
doneOn: { commandExecuted: 'workbench.action.openSettings' },
media: { type: 'image', altText: 'VS Code Settings', path: 'settings.png' },
},
{
id: 'videoTutorial',
title: localize('gettingStarted.videoTutorial.title', "Lean back and learn"),
description: localize('gettingStarted.videoTutorial.description', "Watch the first in a series of short & practical video tutorials for VS Code's key features."),
button: {
title: localize('gettingStarted.videoTutorial.button', "Watch Tutorial"),
link: 'https://aka.ms/vscode-getting-started-video'
},
doneOn: { eventFired: 'linkOpened:https://aka.ms/vscode-getting-started-video' },
media: { type: 'image', altText: 'VS Code Settings', path: 'tutorialVideo.png' },
}
]
}
},
{
id: 'Advanced',
title: localize('gettingStarted.advanced.title', "Tips & Tricks"),
codicon: 'tools',
description: localize('gettingStarted.advanced.description', "Favorites from VS Code experts"),
content: {
type: 'items',
items: []
}
},
{
id: 'OpenFolder-Mac',
title: localize('gettingStarted.openFolder.title', "Open Folder"),
codicon: 'folder-opened',
when: 'isMac',
description: localize('gettingStarted.openFolder.description', "Open a project and start working"),
content: {
type: 'command',
command: 'workbench.action.files.openFileFolder'
}
},
{
id: 'OpenFolder-Other',
title: localize('gettingStarted.openFolder.title', "Open Folder"),
codicon: 'folder-opened',
description: localize('gettingStarted.openFolder.description', "Open a project and start working"),
when: '!isMac',
content: {
type: 'command',
command: 'workbench.action.files.openFolder'
}
},
{
id: 'InteractivePlayground',
title: localize('gettingStarted.playground.title', "Interactive Playground"),
codicon: 'library',
description: localize('gettingStarted.interactivePlayground.description', "Learn essential editor features"),
content: {
type: 'command',
command: 'workbench.action.showInteractivePlayground'
}
}
];

View File

@@ -8,6 +8,7 @@ import { FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { content } from 'vs/workbench/services/gettingStarted/common/gettingStartedContent';
export const enum GettingStartedCategory {
@@ -23,7 +24,9 @@ export interface IGettingStartedTask {
category: GettingStartedCategory | string,
when: ContextKeyExpression,
order: number,
button: { title: string, command: string },
button:
| { title: string, command?: never, link: string }
| { title: string, command: string, link?: never },
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, }
media: { type: 'image', path: URI, altText: string },
}
@@ -32,7 +35,7 @@ export interface IGettingStartedCategoryDescriptor {
id: GettingStartedCategory | string
title: string
description: string
codicon: string
icon: ThemeIcon
when: ContextKeyExpression
content:
| { type: 'items' }
@@ -43,7 +46,7 @@ export interface IGettingStartedCategory {
id: GettingStartedCategory | string
title: string
description: string
codicon: string
icon: ThemeIcon
when: ContextKeyExpression
content:
| { type: 'items', items: IGettingStartedTask[] }

View File

@@ -12,6 +12,8 @@ import { Memento } from 'vs/workbench/common/memento';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
export const IGettingStartedService = createDecorator<IGettingStartedService>('gettingStartedService');
@@ -43,7 +45,7 @@ export interface IGettingStartedService {
progressByEvent(eventName: string): void;
}
export class GettingStartedService implements IGettingStartedService {
export class GettingStartedService extends Disposable implements IGettingStartedService {
declare readonly _serviceBrand: undefined;
private readonly _onDidAddTask = new Emitter<IGettingStartedTaskWithProgress>();
@@ -65,7 +67,10 @@ export class GettingStartedService implements IGettingStartedService {
@IStorageService private readonly storageService: IStorageService,
@ICommandService private readonly commandService: ICommandService,
@IContextKeyService private readonly contextService: IContextKeyService,
@IUserDataAutoSyncEnablementService readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
) {
super();
this.memento = new Memento('gettingStartedService', this.storageService);
this.taskProgress = this.memento.getMemento(StorageScope.GLOBAL, StorageTarget.USER);
@@ -75,13 +80,17 @@ export class GettingStartedService implements IGettingStartedService {
}
});
this.registry.onDidAddCategory(category => this._onDidAddCategory.fire(this.getCategoryProgress(category)));
this.registry.onDidAddTask(task => {
this._register(this.registry.onDidAddCategory(category => this._onDidAddCategory.fire(this.getCategoryProgress(category))));
this._register(this.registry.onDidAddTask(task => {
this.registerDoneListeners(task);
this._onDidAddTask.fire(this.getTaskProgress(task));
});
}));
this.commandService.onDidExecuteCommand(command => this.progressByCommand(command.commandId));
this._register(this.commandService.onDidExecuteCommand(command => this.progressByCommand(command.commandId)));
this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => {
if (userDataAutoSyncEnablementService.isEnabled()) { this.progressByEvent('sync-enabled'); }
}));
}
private registerDoneListeners(task: IGettingStartedTask) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -25,6 +25,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BeforeShutdownEvent, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
/**
* A workspace to open in the workbench can either be:
@@ -126,8 +127,7 @@ export class BrowserHostService extends Disposable implements IHostService {
case HostShutdownReason.Keyboard:
const confirmBeforeClose = this.configurationService.getValue<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose');
if (confirmBeforeClose === 'always' || (confirmBeforeClose === 'keyboardOnly' && this.shutdownReason === HostShutdownReason.Keyboard)) {
this.logService.warn(`Unload veto: window.confirmBeforeClose=${confirmBeforeClose}`);
e.veto(true);
e.veto(true, 'veto.confirmBeforeClose');
}
break;
@@ -339,7 +339,7 @@ export class BrowserHostService extends Disposable implements IHostService {
}
if (isWorkspaceToOpen(openable)) {
return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: true });
return this.labelService.getWorkspaceLabel(getWorkspaceIdentifier(openable.workspaceUri), { verbose: true });
}
return this.labelService.getUriLabel(openable.fileUri);

View File

@@ -50,6 +50,7 @@ import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { dirname } from 'vs/base/common/resources';
import { getAllUnboundCommands } from 'vs/workbench/services/keybinding/browser/unboundCommands';
interface ContributedKeyBinding {
command: string;
@@ -594,7 +595,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
}
private static _getAllCommandsAsComment(boundCommands: Map<string, boolean>): string {
const unboundCommands = KeybindingResolver.getAllUnboundCommands(boundCommands);
const unboundCommands = getAllUnboundCommands(boundCommands);
let pretty = unboundCommands.sort().join('\n// - ');
return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty;
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
export function getAllUnboundCommands(boundCommands: Map<string, boolean>): string[] {
const unboundCommands: string[] = [];
const seenMap: Map<string, boolean> = new Map<string, boolean>();
const addCommand = (id: string, includeCommandWithArgs: boolean) => {
if (seenMap.has(id)) {
return;
}
seenMap.set(id, true);
if (id[0] === '_' || id.indexOf('vscode.') === 0) { // private command
return;
}
if (boundCommands.get(id) === true) {
return;
}
if (!includeCommandWithArgs) {
const command = CommandsRegistry.getCommand(id);
if (command && typeof command.description === 'object'
&& isNonEmptyArray((<ICommandHandlerDescription>command.description).args)) { // command with args
return;
}
}
unboundCommands.push(id);
};
// Add all commands from Command Palette
for (const menuItem of MenuRegistry.getMenuItems(MenuId.CommandPalette)) {
if (isIMenuItem(menuItem)) {
addCommand(menuItem.command.id, true);
}
}
// Add all editor actions
for (const editorAction of EditorExtensionsRegistry.getEditorActions()) {
addCommand(editorAction.id, true);
}
for (const id of CommandsRegistry.getCommands().keys()) {
addCommand(id, false);
}
return unboundCommands;
}

View File

@@ -66,32 +66,11 @@ export class WindowsNativeResolvedKeybinding extends BaseResolvedKeybinding<Simp
return this._mapper.getAriaLabelForKeyCode(keybinding.keyCode);
}
private _keyCodeToElectronAccelerator(keyCode: KeyCode): string | null {
if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) {
// Electron cannot handle numpad keys
return null;
}
switch (keyCode) {
case KeyCode.UpArrow:
return 'Up';
case KeyCode.DownArrow:
return 'Down';
case KeyCode.LeftArrow:
return 'Left';
case KeyCode.RightArrow:
return 'Right';
}
// electron menus always do the correct rendering on Windows
return KeyCodeUtils.toString(keyCode);
}
protected _getElectronAccelerator(keybinding: SimpleKeybinding): string | null {
if (keybinding.isDuplicateModifierCase()) {
return null;
}
return this._keyCodeToElectronAccelerator(keybinding.keyCode);
return this._mapper.getElectronAcceleratorForKeyBinding(keybinding);
}
protected _getUserSettingsLabel(keybinding: SimpleKeybinding): string | null {
@@ -409,6 +388,53 @@ export class WindowsKeyboardMapper implements IKeyboardMapper {
return KeyCodeUtils.toUserSettingsGeneral(keyCode);
}
public getElectronAcceleratorForKeyBinding(keybinding: SimpleKeybinding): string | null {
if (!this.isUSStandard) {
// See https://github.com/electron/electron/issues/26888
// Electron does not render accelerators respecting the current keyboard layout since 3.x
const keyCode = keybinding.keyCode;
const isOEMKey = (
keyCode === KeyCode.US_SEMICOLON
|| keyCode === KeyCode.US_EQUAL
|| keyCode === KeyCode.US_COMMA
|| keyCode === KeyCode.US_MINUS
|| keyCode === KeyCode.US_DOT
|| keyCode === KeyCode.US_SLASH
|| keyCode === KeyCode.US_BACKTICK
|| keyCode === KeyCode.US_OPEN_SQUARE_BRACKET
|| keyCode === KeyCode.US_BACKSLASH
|| keyCode === KeyCode.US_CLOSE_SQUARE_BRACKET
|| keyCode === KeyCode.OEM_8
|| keyCode === KeyCode.OEM_102
);
if (isOEMKey) {
return null;
}
}
return this._keyCodeToElectronAccelerator(keybinding.keyCode);
}
private _keyCodeToElectronAccelerator(keyCode: KeyCode): string | null {
if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) {
// Electron cannot handle numpad keys
return null;
}
switch (keyCode) {
case KeyCode.UpArrow:
return 'Up';
case KeyCode.DownArrow:
return 'Down';
case KeyCode.LeftArrow:
return 'Left';
case KeyCode.RightArrow:
return 'Right';
}
// electron menus always do the correct rendering on Windows
return KeyCodeUtils.toString(keyCode);
}
private _getLabelForKeyCode(keyCode: KeyCode): string {
return this._keyCodeToLabel[keyCode] || KeyCodeUtils.toString(KeyCode.Unknown);
}

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin'; // 15%
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin';
import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution';
import { BrowserKeyboardMapperFactoryBase } from 'vs/workbench/services/keybinding/browser/keyboardLayoutService';
@@ -41,12 +41,12 @@ suite('keyboard layout loader', () => {
let instance = new TestKeyboardMapperFactory(notitifcationService, storageService, commandService);
test('load default US keyboard layout', () => {
assert.notEqual(instance.activeKeyboardLayout, null);
assert.notStrictEqual(instance.activeKeyboardLayout, null);
});
test('isKeyMappingActive', () => {
instance.setUSKeyboardLayout();
assert.equal(instance.isKeyMappingActive({
assert.strictEqual(instance.isKeyMappingActive({
KeyA: {
value: 'a',
valueIsDeadKey: false,
@@ -59,7 +59,7 @@ suite('keyboard layout loader', () => {
}
}), true);
assert.equal(instance.isKeyMappingActive({
assert.strictEqual(instance.isKeyMappingActive({
KeyA: {
value: 'a',
valueIsDeadKey: false,
@@ -82,7 +82,7 @@ suite('keyboard layout loader', () => {
}
}), true);
assert.equal(instance.isKeyMappingActive({
assert.strictEqual(instance.isKeyMappingActive({
KeyZ: {
value: 'y',
valueIsDeadKey: false,
@@ -110,8 +110,8 @@ suite('keyboard layout loader', () => {
withShiftAltGrIsDeadKey: false
}
});
assert.equal(!!instance.activeKeyboardLayout!.isUSStandard, false);
assert.equal(instance.isKeyMappingActive({
assert.strictEqual(!!instance.activeKeyboardLayout!.isUSStandard, false);
assert.strictEqual(instance.isKeyMappingActive({
KeyZ: {
value: 'y',
valueIsDeadKey: false,
@@ -125,13 +125,13 @@ suite('keyboard layout loader', () => {
}), true);
instance.setUSKeyboardLayout();
assert.equal(instance.activeKeyboardLayout!.isUSStandard, true);
assert.strictEqual(instance.activeKeyboardLayout!.isUSStandard, true);
});
test('Switch keyboard layout info', () => {
instance.setKeyboardLayout('com.apple.keylayout.German');
assert.equal(!!instance.activeKeyboardLayout!.isUSStandard, false);
assert.equal(instance.isKeyMappingActive({
assert.strictEqual(!!instance.activeKeyboardLayout!.isUSStandard, false);
assert.strictEqual(instance.isKeyMappingActive({
KeyZ: {
value: 'y',
valueIsDeadKey: false,
@@ -145,6 +145,6 @@ suite('keyboard layout loader', () => {
}), true);
instance.setUSKeyboardLayout();
assert.equal(instance.activeKeyboardLayout!.isUSStandard, true);
assert.strictEqual(instance.activeKeyboardLayout!.isUSStandard, true);
});
});

View File

@@ -0,0 +1,315 @@
/*---------------------------------------------------------------------------------------------
* 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 * as json from 'vs/base/common/json';
import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { OS } from 'vs/base/common/platform';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestPathService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILabelService } from 'vs/platform/label/common/label';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { joinPath } from 'vs/base/common/resources';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
interface Modifiers {
metaKey?: boolean;
ctrlKey?: boolean;
altKey?: boolean;
shiftKey?: boolean;
}
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
suite('KeybindingsEditing', () => {
const disposables = new DisposableStore();
let instantiationService: TestInstantiationService, fileService: IFileService, environmentService: IEnvironmentService;
let testObject: KeybindingsEditingService;
setup(async () => {
const logService = new NullLogService();
fileService = disposables.add(new FileService(logService));
const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));
const userFolder = joinPath(ROOT, 'User');
await fileService.createFolder(userFolder);
environmentService = TestEnvironmentService;
instantiationService = new TestInstantiationService();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'eol': '\n' });
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IPathService, new TestPathService());
instantiationService.stub(IConfigurationService, configService);
instantiationService.stub(IWorkspaceContextService, new TestContextService());
const lifecycleService = new TestLifecycleService();
instantiationService.stub(ILifecycleService, lifecycleService);
instantiationService.stub(IContextKeyService, <IContextKeyService>instantiationService.createInstance(MockContextKeyService));
instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService());
instantiationService.stub(IEditorService, new TestEditorService());
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(IModeService, ModeServiceImpl);
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService)));
instantiationService.stub(IFilesConfigurationService, disposables.add(instantiationService.createInstance(FilesConfigurationService)));
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService)));
instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
instantiationService.stub(IThemeService, new TestThemeService());
instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelServiceImpl)));
fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService())));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService));
instantiationService.stub(IWorkingCopyService, disposables.add(new TestWorkingCopyService()));
instantiationService.stub(IWorkingCopyFileService, disposables.add(instantiationService.createInstance(WorkingCopyFileService)));
instantiationService.stub(ITextFileService, disposables.add(instantiationService.createInstance(TestTextFileService)));
instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService)));
instantiationService.stub(IBackupFileService, new TestBackupFileService());
testObject = disposables.add(instantiationService.createInstance(KeybindingsEditingService));
});
teardown(() => disposables.clear());
test('errors cases - parse errors', async () => {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(',,,,,,,,,,,,,,'));
try {
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
assert.fail('Should fail with parse errors');
} catch (error) {
assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.');
}
});
test('errors cases - parse errors 2', async () => {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString('[{"key": }]'));
try {
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
assert.fail('Should fail with parse errors');
} catch (error) {
assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.');
}
});
test('errors cases - dirty', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with dirty error'),
error => assert.equal(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.'));
});
test('errors cases - did not find an array', async () => {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString('{"key": "alt+c", "command": "hello"}'));
try {
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
assert.fail('Should fail');
} catch (error) {
assert.equal(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.');
}
});
test('edit a default keybinding to an empty file', async () => {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(''));
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
test('edit a default keybinding to an empty array', async () => {
await writeToKeybindingsFile();
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
return assert.deepEqual(await getUserKeybindings(), expected);
});
test('edit a default keybinding in an existing array', async () => {
await writeToKeybindingsFile({ command: 'b', key: 'shift+c' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
return assert.deepEqual(await getUserKeybindings(), expected);
});
test('add another keybinding', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
await testObject.addKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
return assert.deepEqual(await getUserKeybindings(), expected);
});
test('add a new default keybinding', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
await testObject.addKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined);
return assert.deepEqual(await getUserKeybindings(), expected);
});
test('add a new default keybinding using edit', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
test('edit an user keybinding', async () => {
await writeToKeybindingsFile({ key: 'escape', command: 'b' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
test('edit an user keybinding with more than one element', async () => {
await writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
test('remove a default keybinding', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
assert.deepEqual(await getUserKeybindings(), expected);
});
test('remove a default keybinding should not ad duplicate entries', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
assert.deepEqual(await getUserKeybindings(), expected);
});
test('remove a user keybinding', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }));
assert.deepEqual(await getUserKeybindings(), []);
});
test('reset an edited keybinding', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }));
assert.deepEqual(await getUserKeybindings(), []);
});
test('reset a removed keybinding', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }));
assert.deepEqual(await getUserKeybindings(), []);
});
test('reset multiple removed keybindings', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
await writeToKeybindingsFile({ key: 'alt+shift+c', command: '-b' });
await writeToKeybindingsFile({ key: 'escape', command: '-b' });
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }));
assert.deepEqual(await getUserKeybindings(), []);
});
test('add a new keybinding to unassigned keybinding', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
test('add when expression', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus');
assert.deepEqual(await getUserKeybindings(), expected);
});
test('update command and when expression', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus');
assert.deepEqual(await getUserKeybindings(), expected);
});
test('update when expression', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false, when: 'editorTextFocus && !editorReadonly' }), 'shift+alt+c', 'editorTextFocus');
assert.deepEqual(await getUserKeybindings(), expected);
});
test('remove when expression', async () => {
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a' }];
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined);
assert.deepEqual(await getUserKeybindings(), expected);
});
async function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]): Promise<void> {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify(keybindings || [])));
}
async function getUserKeybindings(): Promise<IUserFriendlyKeybinding[]> {
return json.parse((await fileService.readFile(environmentService.keybindingsResource)).value.toString());
}
function aResolvedKeybindingItem({ command, when, isDefault, firstPart, chordPart }: { command?: string, when?: string, isDefault?: boolean, firstPart?: { keyCode: KeyCode, modifiers?: Modifiers }, chordPart?: { keyCode: KeyCode, modifiers?: Modifiers } }): ResolvedKeybindingItem {
const aSimpleKeybinding = function (part: { keyCode: KeyCode, modifiers?: Modifiers }): SimpleKeybinding {
const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
return new SimpleKeybinding(ctrlKey!, shiftKey!, altKey!, metaKey!, part.keyCode);
};
let parts: SimpleKeybinding[] = [];
if (firstPart) {
parts.push(aSimpleKeybinding(firstPart));
if (chordPart) {
parts.push(aSimpleKeybinding(chordPart));
}
}
const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined;
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault, null, false);
}
});

View File

@@ -18,7 +18,7 @@ suite('keybindingIO', () => {
function testOneSerialization(keybinding: number, expected: string, msg: string, OS: OperatingSystem): void {
let usLayoutResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS);
let actualSerialized = usLayoutResolvedKeybinding.getUserSettingsLabel();
assert.equal(actualSerialized, expected, expected + ' - ' + msg);
assert.strictEqual(actualSerialized, expected, expected + ' - ' + msg);
}
function testSerialization(keybinding: number, expectedWin: string, expectedMac: string, expectedLinux: string): void {
testOneSerialization(keybinding, expectedWin, 'win', OperatingSystem.Windows);
@@ -29,7 +29,7 @@ suite('keybindingIO', () => {
function testOneDeserialization(keybinding: string, _expected: number, msg: string, OS: OperatingSystem): void {
let actualDeserialized = KeybindingParser.parseKeybinding(keybinding, OS);
let expected = createKeybinding(_expected, OS);
assert.deepEqual(actualDeserialized, expected, keybinding + ' - ' + msg);
assert.deepStrictEqual(actualDeserialized, expected, keybinding + ' - ' + msg);
}
function testDeserialization(inWin: string, inMac: string, inLinux: string, expected: number): void {
testOneDeserialization(inWin, expected, 'win', OperatingSystem.Windows);
@@ -117,7 +117,7 @@ suite('keybindingIO', () => {
});
test('deserialize scan codes', () => {
assert.deepEqual(
assert.deepStrictEqual(
KeybindingParser.parseUserBinding('ctrl+shift+[comma] ctrl+/'),
[new ScanCodeBinding(true, true, false, false, ScanCode.Comma), new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH)]
);
@@ -127,34 +127,34 @@ suite('keybindingIO', () => {
let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": ["firstcommand", "seccondcommand"] }]`;
let userKeybinding = <IUserFriendlyKeybinding>JSON.parse(strJSON)[0];
let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding);
assert.equal(keybindingItem.command, null);
assert.strictEqual(keybindingItem.command, null);
});
test('issue #10452 - invalid when', () => {
let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [] }]`;
let userKeybinding = <IUserFriendlyKeybinding>JSON.parse(strJSON)[0];
let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding);
assert.equal(keybindingItem.when, null);
assert.strictEqual(keybindingItem.when, undefined);
});
test('issue #10452 - invalid key', () => {
let strJSON = `[{ "key": [], "command": "firstcommand" }]`;
let userKeybinding = <IUserFriendlyKeybinding>JSON.parse(strJSON)[0];
let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding);
assert.deepEqual(keybindingItem.parts, []);
assert.deepStrictEqual(keybindingItem.parts, []);
});
test('issue #10452 - invalid key 2', () => {
let strJSON = `[{ "key": "", "command": "firstcommand" }]`;
let userKeybinding = <IUserFriendlyKeybinding>JSON.parse(strJSON)[0];
let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding);
assert.deepEqual(keybindingItem.parts, []);
assert.deepStrictEqual(keybindingItem.parts, []);
});
test('test commands args', () => {
let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [], "args": { "text": "theText" } }]`;
let userKeybinding = <IUserFriendlyKeybinding>JSON.parse(strJSON)[0];
let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding);
assert.equal(keybindingItem.commandArgs.text, 'theText');
assert.strictEqual(keybindingItem.commandArgs.text, 'theText');
});
});

View File

@@ -1,330 +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 * as assert from 'assert';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as json from 'vs/base/common/json';
import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { OS } from 'vs/base/common/platform';
import * as uuid from 'vs/base/common/uuid';
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestPathService, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { URI } from 'vs/base/common/uri';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { ILabelService } from 'vs/platform/label/common/label';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(private _appSettingsHome: URI) {
super(TestWorkbenchConfiguration, TestProductService);
}
get appSettingsHome() { return this._appSettingsHome; }
}
interface Modifiers {
metaKey?: boolean;
ctrlKey?: boolean;
altKey?: boolean;
shiftKey?: boolean;
}
suite('KeybindingsEditing', () => {
let instantiationService: TestInstantiationService;
let testObject: KeybindingsEditingService;
let testDir: string;
let keybindingsFile: string;
setup(() => {
return setUpWorkspace().then(() => {
keybindingsFile = path.join(testDir, 'keybindings.json');
instantiationService = new TestInstantiationService();
const environmentService = new TestWorkbenchEnvironmentService(URI.file(testDir));
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'eol': '\n' });
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IPathService, new TestPathService());
instantiationService.stub(IConfigurationService, configService);
instantiationService.stub(IWorkspaceContextService, new TestContextService());
const lifecycleService = new TestLifecycleService();
instantiationService.stub(ILifecycleService, lifecycleService);
instantiationService.stub(IContextKeyService, <IContextKeyService>instantiationService.createInstance(MockContextKeyService));
instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService());
instantiationService.stub(IEditorService, new TestEditorService());
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(IModeService, ModeServiceImpl);
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService));
instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService));
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService)));
instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
instantiationService.stub(IThemeService, new TestThemeService());
instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, new NullLogService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService));
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
instantiationService.stub(IBackupFileService, new TestBackupFileService());
testObject = instantiationService.createInstance(KeybindingsEditingService);
});
});
async function setUpWorkspace(): Promise<void> {
testDir = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid());
return await mkdirp(testDir, 493);
}
teardown(() => {
return new Promise<void>((c) => {
if (testDir) {
rimraf(testDir, RimRafMode.MOVE).then(c, c);
} else {
c(undefined);
}
}).then(() => testDir = null!);
});
test('errors cases - parse errors', () => {
fs.writeFileSync(keybindingsFile, ',,,,,,,,,,,,,,');
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with parse errors'),
error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'));
});
test('errors cases - parse errors 2', () => {
fs.writeFileSync(keybindingsFile, '[{"key": }]');
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with parse errors'),
error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'));
});
test('errors cases - dirty', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with dirty error'),
error => assert.equal(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.'));
});
test('errors cases - did not find an array', () => {
fs.writeFileSync(keybindingsFile, '{"key": "alt+c", "command": "hello"}');
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with dirty error'),
error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.'));
});
test('edit a default keybinding to an empty file', () => {
fs.writeFileSync(keybindingsFile, '');
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('edit a default keybinding to an empty array', () => {
writeToKeybindingsFile();
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('edit a default keybinding in an existing array', () => {
writeToKeybindingsFile({ command: 'b', key: 'shift+c' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('add another keybinding', () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
return testObject.addKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('add a new default keybinding', () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
return testObject.addKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('add a new default keybinding using edit', () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('edit an user keybinding', () => {
writeToKeybindingsFile({ key: 'escape', command: 'b' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('edit an user keybinding with more than one element', () => {
writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('remove a default keybinding', () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }))
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('remove a default keybinding should not ad duplicate entries', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
assert.deepEqual(getUserKeybindings(), expected);
});
test('remove a user keybinding', () => {
writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }))
.then(() => assert.deepEqual(getUserKeybindings(), []));
});
test('reset an edited keybinding', () => {
writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }))
.then(() => assert.deepEqual(getUserKeybindings(), []));
});
test('reset a removed keybinding', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }))
.then(() => assert.deepEqual(getUserKeybindings(), []));
});
test('reset multiple removed keybindings', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
writeToKeybindingsFile({ key: 'alt+shift+c', command: '-b' });
writeToKeybindingsFile({ key: 'escape', command: '-b' });
return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }))
.then(() => assert.deepEqual(getUserKeybindings(), []));
});
test('add a new keybinding to unassigned keybinding', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('add when expression', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus')
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('update command and when expression', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus')
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('update when expression', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false, when: 'editorTextFocus && !editorReadonly' }), 'shift+alt+c', 'editorTextFocus')
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('remove when expression', () => {
writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a' }];
return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined)
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]) {
fs.writeFileSync(keybindingsFile, JSON.stringify(keybindings || []));
}
function getUserKeybindings(): IUserFriendlyKeybinding[] {
return json.parse(fs.readFileSync(keybindingsFile).toString('utf8'));
}
function aResolvedKeybindingItem({ command, when, isDefault, firstPart, chordPart }: { command?: string, when?: string, isDefault?: boolean, firstPart?: { keyCode: KeyCode, modifiers?: Modifiers }, chordPart?: { keyCode: KeyCode, modifiers?: Modifiers } }): ResolvedKeybindingItem {
const aSimpleKeybinding = function (part: { keyCode: KeyCode, modifiers?: Modifiers }): SimpleKeybinding {
const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
return new SimpleKeybinding(ctrlKey!, shiftKey!, altKey!, metaKey!, part.keyCode);
};
let parts: SimpleKeybinding[] = [];
if (firstPart) {
parts.push(aSimpleKeybinding(firstPart));
if (chordPart) {
parts.push(aSimpleKeybinding(chordPart));
}
}
const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined;
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault, null, false);
}
});

View File

@@ -65,13 +65,12 @@ export function assertMapping(writeFileIfDifferent: boolean, mapper: IKeyboardMa
const filePath = path.normalize(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}`));
return readFile(filePath).then((buff) => {
let expected = buff.toString();
const actual = mapper.dumpDebugInfo();
const expected = buff.toString().replace(/\r\n/g, '\n');
const actual = mapper.dumpDebugInfo().replace(/\r\n/g, '\n');
if (actual !== expected && writeFileIfDifferent) {
const destPath = filePath.replace(/vscode[\/\\]out[\/\\]vs/, 'vscode/src/vs');
writeFile(destPath, actual);
}
assert.deepEqual(actual.split(/\r\n|\n/), expected.split(/\r\n|\n/));
assert.deepEqual(actual, expected);
});
}

View File

@@ -1465,11 +1465,11 @@ function _assertKeybindingTranslation(mapper: MacLinuxKeyboardMapper, OS: Operat
const actualHardwareKeypresses = mapper.simpleKeybindingToScanCodeBinding(runtimeKeybinding);
if (actualHardwareKeypresses.length === 0) {
assert.deepEqual([], expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "[]" -- expected: "${expected}"`);
assert.deepStrictEqual([], expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "[]" -- expected: "${expected}"`);
return;
}
const actual = actualHardwareKeypresses
.map(k => UserSettingsLabelProvider.toLabel(OS, [k], (keybinding) => ScanCodeUtils.toString(keybinding.scanCode)));
assert.deepEqual(actual, expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "${actual}" -- expected: "${expected}"`);
assert.deepStrictEqual(actual, expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "${actual}" -- expected: "${expected}"`);
}

View File

@@ -97,7 +97,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
[{
label: 'Ctrl+^',
ariaLabel: 'Control+^',
electronAccelerator: 'Ctrl+]',
electronAccelerator: null,
userSettingsLabel: 'ctrl+oem_6',
isWYSIWYG: false,
isChord: false,
@@ -121,7 +121,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
{
label: 'Ctrl+^',
ariaLabel: 'Control+^',
electronAccelerator: 'Ctrl+]',
electronAccelerator: null,
userSettingsLabel: 'ctrl+oem_6',
isWYSIWYG: false,
isChord: false,
@@ -137,7 +137,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
[{
label: 'Shift+^',
ariaLabel: 'Shift+^',
electronAccelerator: 'Shift+]',
electronAccelerator: null,
userSettingsLabel: 'shift+oem_6',
isWYSIWYG: false,
isChord: false,
@@ -153,7 +153,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
[{
label: 'Ctrl+§',
ariaLabel: 'Control+§',
electronAccelerator: 'Ctrl+/',
electronAccelerator: null,
userSettingsLabel: 'ctrl+oem_2',
isWYSIWYG: false,
isChord: false,
@@ -169,7 +169,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => {
[{
label: 'Ctrl+Shift+§',
ariaLabel: 'Control+Shift+§',
electronAccelerator: 'Ctrl+Shift+/',
electronAccelerator: null,
userSettingsLabel: 'ctrl+shift+oem_2',
isWYSIWYG: false,
isChord: false,

View File

@@ -11,16 +11,17 @@ import { Emitter } from 'vs/base/common/event';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspace, isWorkspace } from 'vs/platform/workspace/common/workspace';
import { basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { match } from 'vs/base/common/glob';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { hasDriveLetter } from 'vs/base/common/extpath';
const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint<ResourceLabelFormatter[]>({
extensionPoint: 'resourceLabelFormatters',
@@ -73,10 +74,6 @@ const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoin
const sepRegexp = /\//g;
const labelMatchingRegexp = /\$\{(scheme|authority|path|(query)\.(.+?))\}/g;
function hasDriveLetter(path: string): boolean {
return !!(path && path[2] === ':');
}
class ResourceLabelFormattersHandler implements IWorkbenchContribution {
private formattersDisposables = new Map<ResourceLabelFormatter, IDisposable>();
@@ -115,7 +112,7 @@ export class LabelService extends Disposable implements ILabelService {
this.formatters.forEach(formatter => {
if (formatter.scheme === resource.scheme) {
if (!bestResult && !formatter.authority) {
if (!formatter.authority && (!bestResult || formatter.priority)) {
bestResult = formatter;
return;
}
@@ -152,6 +149,8 @@ export class LabelService extends Disposable implements ILabelService {
while (relativeLabel[overlap] && relativeLabel[overlap] === baseResourceLabel[overlap]) { overlap++; }
if (!relativeLabel[overlap] || relativeLabel[overlap] === formatting.separator) {
relativeLabel = relativeLabel.substring(1 + overlap);
} else if (overlap === baseResourceLabel.length && baseResource.uri.path === '/') {
relativeLabel = relativeLabel.substring(overlap);
}
const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1;
@@ -181,44 +180,60 @@ export class LabelService extends Disposable implements ILabelService {
return paths.basename(label);
}
getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string {
if (IWorkspace.isIWorkspace(workspace)) {
getWorkspaceLabel(workspace: IWorkspace | IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, options?: { verbose: boolean }): string {
if (isWorkspace(workspace)) {
const identifier = toWorkspaceIdentifier(workspace);
if (!identifier) {
return '';
if (identifier) {
return this.getWorkspaceLabel(identifier, options);
}
workspace = identifier;
return '';
}
// Workspace: Single Folder
// Workspace: Single Folder (as URI)
if (URI.isUri(workspace)) {
return this.doGetSingleFolderWorkspaceLabel(workspace, options);
}
// Workspace: Single Folder (as workspace identifier)
if (isSingleFolderWorkspaceIdentifier(workspace)) {
// Folder on disk
const label = options && options.verbose ? this.getUriLabel(workspace) : basename(workspace) || '/';
return this.appendWorkspaceSuffix(label, workspace);
return this.doGetSingleFolderWorkspaceLabel(workspace.uri, options);
}
// Workspace: Multi Root
if (isWorkspaceIdentifier(workspace)) {
// Workspace: Untitled
if (isUntitledWorkspace(workspace.configPath, this.environmentService)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
let filename = basename(workspace.configPath);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspace.configPath);
return this.doGetWorkspaceLabel(workspace.configPath, options);
}
return '';
return '';
}
private doGetWorkspaceLabel(workspaceUri: URI, options?: { verbose: boolean }): string {
// Workspace: Untitled
if (isUntitledWorkspace(workspaceUri, this.environmentService)) {
return localize('untitledWorkspace', "Untitled (Workspace)");
}
// Workspace: Saved
let filename = basename(workspaceUri);
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
let label;
if (options && options.verbose) {
label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspaceUri), filename)));
} else {
label = localize('workspaceName', "{0} (Workspace)", filename);
}
return this.appendWorkspaceSuffix(label, workspaceUri);
}
private doGetSingleFolderWorkspaceLabel(folderUri: URI, options?: { verbose: boolean }): string {
const label = options && options.verbose ? this.getUriLabel(folderUri) : basename(folderUri) || '/';
return this.appendWorkspaceSuffix(label, folderUri);
}
getSeparator(scheme: string, authority?: string): '/' | '\\' {
@@ -268,7 +283,7 @@ export class LabelService extends Disposable implements ILabelService {
});
// convert \c:\something => C:\something
if (formatting.normalizeDriveLetter && hasDriveLetter(label)) {
if (formatting.normalizeDriveLetter && hasDriveLetter(label.substr(1))) {
label = label.charAt(1).toUpperCase() + label.substr(2);
}

View File

@@ -160,7 +160,7 @@ suite('URI Label', () => {
});
suite('multi-root worksapce', () => {
suite('multi-root workspace', () => {
let labelService: LabelService;
setup(() => {
@@ -171,7 +171,7 @@ suite('multi-root worksapce', () => {
labelService = new LabelService(
TestEnvironmentService,
new TestContextService(
new Workspace('test-workspaace', [
new Workspace('test-workspace', [
new WorkspaceFolder({ uri: sources, index: 0, name: 'Sources' }, { uri: sources.toString() }),
new WorkspaceFolder({ uri: tests, index: 1, name: 'Tests' }, { uri: tests.toString() }),
new WorkspaceFolder({ uri: other, index: 2, name: resources.basename(other) }, { uri: other.toString() }),
@@ -179,7 +179,7 @@ suite('multi-root worksapce', () => {
new TestPathService());
});
test('labels of files in multiroot workspaces are the foldername folloed by offset from the folder', () => {
test('labels of files in multiroot workspaces are the foldername followed by offset from the folder', () => {
labelService.registerFormatter({
scheme: 'file',
formatting: {
@@ -250,3 +250,57 @@ suite('multi-root worksapce', () => {
});
});
});
suite('workspace at FSP root', () => {
let labelService: LabelService;
setup(() => {
const rootFolder = URI.parse('myscheme://myauthority/');
labelService = new LabelService(
TestEnvironmentService,
new TestContextService(
new Workspace('test-workspace', [
new WorkspaceFolder({ uri: rootFolder, index: 0, name: 'FSProotFolder' }, { uri: rootFolder.toString() }),
])),
new TestPathService());
labelService.registerFormatter({
scheme: 'myscheme',
formatting: {
label: '${scheme}://${authority}${path}',
separator: '/',
tildify: false,
normalizeDriveLetter: false,
workspaceSuffix: '',
authorityPrefix: '',
stripPathStartingSeparator: false
}
});
});
test('non-relative label', () => {
const tests = {
'myscheme://myauthority/myFile1.txt': 'myscheme://myauthority/myFile1.txt',
'myscheme://myauthority/folder/myFile2.txt': 'myscheme://myauthority/folder/myFile2.txt',
};
Object.entries(tests).forEach(([uriString, label]) => {
const generated = labelService.getUriLabel(URI.parse(uriString), { relative: false });
assert.equal(generated, label);
});
});
test('relative label', () => {
const tests = {
'myscheme://myauthority/myFile1.txt': 'myFile1.txt',
'myscheme://myauthority/folder/myFile2.txt': 'folder/myFile2.txt',
};
Object.entries(tests).forEach(([uriString, label]) => {
const generated = labelService.getUriLabel(URI.parse(uriString), { relative: true });
assert.equal(generated, label);
});
});
});

View File

@@ -32,12 +32,12 @@ suite('URI Label', () => {
});
const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') });
assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
assert.equal(labelService.getUriBasenameLabel(uri1), 'd');
assert.strictEqual(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
assert.strictEqual(labelService.getUriBasenameLabel(uri1), 'd');
const uri2 = URI.file('c:\\1/2/3');
assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
assert.equal(labelService.getUriBasenameLabel(uri2), '3');
assert.strictEqual(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
assert.strictEqual(labelService.getUriBasenameLabel(uri2), '3');
});
});

View File

@@ -43,7 +43,7 @@ export function positionToString(position: Position): string {
}
}
const positionsByString: { [key: string]: Position } = {
const positionsByString: { [key: string]: Position; } = {
[positionToString(Position.LEFT)]: Position.LEFT,
[positionToString(Position.RIGHT)]: Position.RIGHT,
[positionToString(Position.BOTTOM)]: Position.BOTTOM
@@ -62,7 +62,7 @@ export function panelOpensMaximizedSettingToString(setting: PanelOpensMaximizedO
}
}
const panelOpensMaximizedByString: { [key: string]: PanelOpensMaximizedOptions } = {
const panelOpensMaximizedByString: { [key: string]: PanelOpensMaximizedOptions; } = {
[panelOpensMaximizedSettingToString(PanelOpensMaximizedOptions.ALWAYS)]: PanelOpensMaximizedOptions.ALWAYS,
[panelOpensMaximizedSettingToString(PanelOpensMaximizedOptions.NEVER)]: PanelOpensMaximizedOptions.NEVER,
[panelOpensMaximizedSettingToString(PanelOpensMaximizedOptions.REMEMBER_LAST)]: PanelOpensMaximizedOptions.REMEMBER_LAST
@@ -106,6 +106,11 @@ export interface IWorkbenchLayoutService extends ILayoutService {
*/
readonly onPartVisibilityChange: Event<void>;
/**
* Run a layout of the workbench.
*/
layout(): void;
/**
* Asks the part service if all parts have been fully restored. For editor part
* this means that the contents of editors have loaded.
@@ -194,6 +199,11 @@ export interface IWorkbenchLayoutService extends ILayoutService {
*/
getMenubarVisibility(): MenuBarVisibility;
/**
* Toggles the menu bar visibility.
*/
toggleMenuBar(): void;
/**
* Gets the current panel position. Note that the panel can be hidden too.
*/

View File

@@ -16,6 +16,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
declare readonly _serviceBrand: undefined;
private beforeUnloadDisposable: IDisposable | undefined = undefined;
private expectedUnload = false;
constructor(
@ILogService readonly logService: ILogService
@@ -32,19 +33,37 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
}
private onBeforeUnload(event: BeforeUnloadEvent): void {
if (this.expectedUnload) {
this.logService.info('[lifecycle] onBeforeUnload expected, ignoring once');
this.expectedUnload = false;
return; // ignore expected unload only once
}
this.logService.info('[lifecycle] onBeforeUnload triggered');
this.doShutdown(() => {
// Veto handling
event.preventDefault();
event.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again.");
});
}
withExpectedUnload(callback: Function): void {
this.expectedUnload = true;
try {
callback();
} finally {
this.expectedUnload = false;
}
}
shutdown(): void {
this.logService.info('[lifecycle] shutdown triggered');
// Remove beforeunload listener that would prevent shutdown
// Remove `beforeunload` listener that would prevent shutdown
this.beforeUnloadDisposable?.dispose();
// Handle shutdown without veto support
@@ -58,12 +77,17 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
// Before Shutdown
this._onBeforeShutdown.fire({
veto(value) {
veto(value, id) {
if (typeof handleVeto === 'function') {
if (value instanceof Promise) {
logService.error(`[lifecycle] Long running operations before shutdown are unsupported in the web (id: ${id})`);
value = true; // implicitly vetos since we cannot handle promises in web
}
if (value === true) {
veto = true;
} else if (value instanceof Promise && !veto) {
logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web');
logService.info(`[lifecycle]: Unload was prevented (id: ${id})`);
veto = true;
}
}
@@ -80,8 +104,8 @@ export class BrowserLifecycleService extends AbstractLifecycleService {
// No Veto: continue with Will Shutdown
this._onWillShutdown.fire({
join() {
logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web');
join(promise, id) {
logService.error(`[lifecycle] Long running operations during shutdown are unsupported in the web (id: ${id})`);
},
reason: ShutdownReason.QUIT
});

View File

@@ -22,8 +22,11 @@ export interface BeforeShutdownEvent {
/**
* Allows to veto the shutdown. The veto can be a long running operation but it
* will block the application from closing.
*
* @param id to identify the veto operation in case it takes very long or never
* completes.
*/
veto(value: boolean | Promise<boolean>): void;
veto(value: boolean | Promise<boolean>, id: string): void;
/**
* The reason why the application will be shutting down.
@@ -44,8 +47,11 @@ export interface WillShutdownEvent {
/**
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*
* @param id to identify the join operation in case it takes very long or never
* completes.
*/
join(promise: Promise<void>): void;
join(promise: Promise<void>, id: string): void;
/**
* The reason why the application is shutting down.

View File

@@ -49,7 +49,7 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi
this.logService.trace(`lifecycle: phase changed (value: ${value})`);
this._phase = value;
mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`);
mark(`code/LifecyclePhase/${LifecyclePhaseToString(value)}`);
const barrier = this.phaseWhen.get(this._phase);
if (barrier) {

View File

@@ -16,11 +16,15 @@ import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import Severity from 'vs/base/common/severity';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { disposableTimeout } from 'vs/base/common/async';
export class NativeLifecycleService extends AbstractLifecycleService {
private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason';
private static readonly BEFORE_SHUTDOWN_WARNING_DELAY = 5000;
private static readonly WILL_SHUTDOWN_WARNING_DELAY = 5000;
declare readonly _serviceBrand: undefined;
private shutdownReason: ShutdownReason | undefined;
@@ -51,7 +55,7 @@ export class NativeLifecycleService extends AbstractLifecycleService {
startupKind = StartupKind.NewWindow;
}
this.logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
this.logService.trace(`[lifecycle] starting up (startup kind: ${this._startupKind})`);
return startupKind;
}
@@ -61,16 +65,16 @@ export class NativeLifecycleService extends AbstractLifecycleService {
// Main side indicates that window is about to unload, check for vetos
ipcRenderer.on('vscode:onBeforeUnload', (event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
this.logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
this.logService.trace(`[lifecycle] onBeforeUnload (reason: ${reply.reason})`);
// trigger onBeforeShutdown events and veto collecting
this.handleBeforeShutdown(reply.reason).then(veto => {
if (veto) {
this.logService.trace('lifecycle: onBeforeUnload prevented via veto');
this.logService.trace('[lifecycle] onBeforeUnload prevented via veto');
ipcRenderer.send(reply.cancelChannel, windowId);
} else {
this.logService.trace('lifecycle: onBeforeUnload continues without veto');
this.logService.trace('[lifecycle] onBeforeUnload continues without veto');
this.shutdownReason = reply.reason;
ipcRenderer.send(reply.okChannel, windowId);
@@ -80,7 +84,7 @@ export class NativeLifecycleService extends AbstractLifecycleService {
// Main side indicates that we will indeed shutdown
ipcRenderer.on('vscode:onWillUnload', async (event: unknown, reply: { replyChannel: string, reason: ShutdownReason }) => {
this.logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
this.logService.trace(`[lifecycle] onWillUnload (reason: ${reply.reason})`);
// trigger onWillShutdown events and joining
await this.handleWillShutdown(reply.reason);
@@ -100,35 +104,71 @@ export class NativeLifecycleService extends AbstractLifecycleService {
});
}
private handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
private async handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
const logService = this.logService;
const vetos: (boolean | Promise<boolean>)[] = [];
const pendingVetos = new Set<string>();
this._onBeforeShutdown.fire({
veto(value) {
veto(value, id) {
vetos.push(value);
},
reason
});
return handleVetos(vetos, error => this.onShutdownError(reason, error));
}
// Log any veto instantly
if (value === true) {
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
}
private async handleWillShutdown(reason: ShutdownReason): Promise<void> {
const joiners: Promise<void>[] = [];
this._onWillShutdown.fire({
join(promise) {
if (promise) {
joiners.push(promise);
// Track promise completion
else if (value instanceof Promise) {
pendingVetos.add(id);
value.then(veto => {
if (veto === true) {
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
}
}).finally(() => pendingVetos.delete(id));
}
},
reason
});
const longRunningBeforeShutdownWarning = disposableTimeout(() => {
logService.warn(`[lifecycle] onBeforeShutdown is taking a long time, pending operations: ${Array.from(pendingVetos).join(', ')}`);
}, NativeLifecycleService.BEFORE_SHUTDOWN_WARNING_DELAY);
try {
return await handleVetos(vetos, error => this.onShutdownError(reason, error));
} finally {
longRunningBeforeShutdownWarning.dispose();
}
}
private async handleWillShutdown(reason: ShutdownReason): Promise<void> {
const joiners: Promise<void>[] = [];
const pendingJoiners = new Set<string>();
this._onWillShutdown.fire({
join(promise, id) {
if (promise) {
joiners.push(promise);
// Track promise completion
pendingJoiners.add(id);
promise.finally(() => pendingJoiners.delete(id));
}
},
reason
});
const longRunningWillShutdownWarning = disposableTimeout(() => {
this.logService.warn(`[lifecycle] onWillShutdown is taking a long time, pending operations: ${Array.from(pendingJoiners).join(', ')}`);
}, NativeLifecycleService.WILL_SHUTDOWN_WARNING_DELAY);
try {
await Promise.all(joiners);
} catch (error) {
this.onShutdownError(reason, error);
} finally {
longRunningWillShutdownWarning.dispose();
}
}

View File

@@ -39,7 +39,7 @@ export class NativeLogService extends DelegatedLogService {
bufferSpdLogService = disposables.add(new BufferLogService(environmentService.configuration.logLevel));
loggers.push(
disposables.add(new ConsoleLogService(environmentService.configuration.logLevel)),
bufferSpdLogService,
bufferSpdLogService
);
}

View File

@@ -76,15 +76,13 @@ export class NotificationService extends Disposable implements INotificationServ
const neverShowAgainAction = toDispose.add(new Action(
'workbench.notification.neverShowAgain',
nls.localize('neverShowAgain', "Don't Show Again"),
undefined, true, () => {
undefined, true, async () => {
// Close notification
handle.close();
// Remember choice
this.storageService.store(id, true, scope, StorageTarget.USER);
return Promise.resolve();
}));
// Insert as primary or secondary action

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IDataSource, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchDataTreeOptions } from 'vs/platform/list/browser/listService';
import { IEditorPane } from 'vs/workbench/common/editor';
export const IOutlineService = createDecorator<IOutlineService>('IOutlineService');
export const enum OutlineTarget {
OutlinePane = 1,
Breadcrumbs = 2,
QuickPick = 4
}
export interface IOutlineService {
_serviceBrand: undefined;
onDidChange: Event<void>;
canCreateOutline(editor: IEditorPane): boolean;
createOutline(editor: IEditorPane, target: OutlineTarget, token: CancellationToken): Promise<IOutline<any> | undefined>;
registerOutlineCreator(creator: IOutlineCreator<any, any>): IDisposable;
}
export interface IOutlineCreator<P extends IEditorPane, E> {
matches(candidate: IEditorPane): candidate is P;
createOutline(editor: P, target: OutlineTarget, token: CancellationToken): Promise<IOutline<E> | undefined>;
}
export interface IBreadcrumbsDataSource<E> {
getBreadcrumbElements(): readonly E[];
}
export interface IOutlineComparator<E> {
compareByPosition(a: E, b: E): number;
compareByType(a: E, b: E): number;
compareByName(a: E, b: E): number;
}
export interface IQuickPickOutlineElement<E> {
readonly element: E;
readonly label: string;
readonly iconClasses?: string[];
readonly ariaLabel?: string;
readonly description?: string;
}
export interface IQuickPickDataSource<E> {
getQuickPickElements(): Iterable<IQuickPickOutlineElement<E>>;
}
export interface IOutlineListConfig<E> {
readonly breadcrumbsDataSource: IBreadcrumbsDataSource<E>;
readonly treeDataSource: IDataSource<IOutline<E>, E>;
readonly delegate: IListVirtualDelegate<E>;
readonly renderers: ITreeRenderer<E, FuzzyScore, any>[];
readonly comparator: IOutlineComparator<E>;
readonly options: IWorkbenchDataTreeOptions<E, FuzzyScore>;
readonly quickPickDataSource: IQuickPickDataSource<E>;
}
export interface OutlineChangeEvent {
affectOnlyActiveElement?: true
}
export interface IOutline<E> {
readonly config: IOutlineListConfig<E>;
readonly outlineKind: string;
readonly isEmpty: boolean;
readonly activeElement: E | undefined;
readonly onDidChange: Event<OutlineChangeEvent>;
reveal(entry: E, options: IEditorOptions, sideBySide: boolean): Promise<void> | void;
preview(entry: E): IDisposable;
captureViewState(): IDisposable;
dispose(): void;
}
export const enum OutlineConfigKeys {
'icons' = 'outline.icons',
'problemsEnabled' = 'outline.problems.enabled',
'problemsColors' = 'outline.problems.colors',
'problemsBadges' = 'outline.problems.badges'
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IEditorPane } from 'vs/workbench/common/editor';
import { IOutline, IOutlineCreator, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
import { Event, Emitter } from 'vs/base/common/event';
class OutlineService implements IOutlineService {
declare _serviceBrand: undefined;
private readonly _factories = new LinkedList<IOutlineCreator<any, any>>();
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
canCreateOutline(pane: IEditorPane): boolean {
for (let factory of this._factories) {
if (factory.matches(pane)) {
return true;
}
}
return false;
}
async createOutline(pane: IEditorPane, target: OutlineTarget, token: CancellationToken): Promise<IOutline<any> | undefined> {
for (let factory of this._factories) {
if (factory.matches(pane)) {
return await factory.createOutline(pane, target, token);
}
}
return undefined;
}
registerOutlineCreator(creator: IOutlineCreator<any, any>): IDisposable {
const rm = this._factories.push(creator);
this._onDidChange.fire();
return toDisposable(() => {
rm();
this._onDidChange.fire();
});
}
}
registerSingleton(IOutlineService, OutlineService, true);

View File

@@ -16,7 +16,8 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor
import { EditorModel } from 'vs/workbench/common/editor';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { getAllUnboundCommands } from 'vs/workbench/services/keybinding/browser/unboundCommands';
import { IKeybindingItemEntry, KeybindingMatches, KeybindingMatch, IKeybindingItem } from 'vs/workbench/services/preferences/common/preferences';
export const KEYBINDING_ENTRY_TEMPLATE_ID = 'keybinding.entry.template';
@@ -24,44 +25,6 @@ const SOURCE_DEFAULT = localize('default', "Default");
const SOURCE_EXTENSION = localize('extension', "Extension");
const SOURCE_USER = localize('user', "User");
export interface KeybindingMatch {
ctrlKey?: boolean;
shiftKey?: boolean;
altKey?: boolean;
metaKey?: boolean;
keyCode?: boolean;
}
export interface KeybindingMatches {
firstPart: KeybindingMatch;
chordPart: KeybindingMatch;
}
export interface IListEntry {
id: string;
templateId: string;
}
export interface IKeybindingItemEntry extends IListEntry {
keybindingItem: IKeybindingItem;
commandIdMatches?: IMatch[];
commandLabelMatches?: IMatch[];
commandDefaultLabelMatches?: IMatch[];
sourceMatches?: IMatch[];
whenMatches?: IMatch[];
keybindingMatches?: KeybindingMatches;
}
export interface IKeybindingItem {
keybinding: ResolvedKeybinding;
keybindingItem: ResolvedKeybindingItem;
commandLabel: string;
commandDefaultLabel: string;
command: string;
source: string;
when: string;
}
interface ModifierLabels {
ui: ModLabels;
aria: ModLabels;
@@ -99,9 +62,9 @@ export class KeybindingsEditorModel extends EditorModel {
.map(keybindingItem => (<IKeybindingItemEntry>{ id: KeybindingsEditorModel.getId(keybindingItem), keybindingItem, templateId: KEYBINDING_ENTRY_TEMPLATE_ID }));
}
if (/@source:\s*(user|default)/i.test(searchValue)) {
if (/@source:\s*(user|default|extension)/i.test(searchValue)) {
keybindingItems = this.filterBySource(keybindingItems, searchValue);
searchValue = searchValue.replace(/@source:\s*(user|default)/i, '');
searchValue = searchValue.replace(/@source:\s*(user|default|extension)/i, '');
} else {
const keybindingMatches = /@keybinding:\s*((\".+\")|(\S+))/i.exec(searchValue);
if (keybindingMatches && (keybindingMatches[2] || keybindingMatches[3])) {
@@ -190,7 +153,7 @@ export class KeybindingsEditorModel extends EditorModel {
}
const commandsWithDefaultKeybindings = this.keybindingsService.getDefaultKeybindings().map(keybinding => keybinding.command);
for (const command of KeybindingResolver.getAllUnboundCommands(boundCommands)) {
for (const command of getAllUnboundCommands(boundCommands)) {
const keybindingItem = new ResolvedKeybindingItem(undefined, command, null, undefined, commandsWithDefaultKeybindings.indexOf(command) === -1, null, false);
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, actionLabels));
}

View File

@@ -10,7 +10,7 @@ import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorInput, SideBySideEditorInput, Verbosity } from 'vs/workbench/common/editor';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/common/keybindingsEditorModel';
import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/browser/keybindingsEditorModel';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';

View File

@@ -30,7 +30,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTargetName, IKeybindingsEditorOptions, IKeybindingsEditorPane, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, SettingsEditorOptions, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences';
import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/browser/preferencesEditorInput';
import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel, DefaultRawSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -246,7 +246,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
jsonEditor;
if (!this.workspaceSettingsResource) {
this.notificationService.info(nls.localize('openFolderFirst', "Open a folder first to create workspace settings"));
this.notificationService.info(nls.localize('openFolderFirst', "Open a folder or workspace first to create workspace or folder settings."));
return Promise.reject(null);
}

Some files were not shown because too many files have changed in this diff Show More