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.
@@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,7 +34,7 @@ export interface IWorkbenchExtensioManagementService extends IExtensionManagemen
|
||||
|
||||
export const enum EnablementState {
|
||||
DisabledByExtensionKind,
|
||||
DisabledByEnvironemt,
|
||||
DisabledByEnvironment,
|
||||
DisabledGlobally,
|
||||
DisabledWorkspace,
|
||||
EnabledGlobally,
|
||||
|
||||
@@ -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[]> {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.'),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
@@ -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[] }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 51 KiB |
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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}"`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||