Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { originalFSPath } from 'vs/base/common/resources';
import { isWindows } from 'vs/base/common/platform';
suite('ExtHost API', function () {
test('issue #51387: originalFSPath', function () {
if (isWindows) {
assert.equal(originalFSPath(URI.file('C:\\test')).charAt(0), 'C');
assert.equal(originalFSPath(URI.file('c:\\test')).charAt(0), 'c');
assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('C:\\test'))))).charAt(0), 'C');
assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('c:\\test'))))).charAt(0), 'c');
}
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* 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 extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { MainContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { NullLogService } from 'vs/platform/log/common/log';
import { assertType } from 'vs/base/common/types';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
const resource = URI.parse('foo:bar');
let bulkEdits: ExtHostBulkEdits;
let workspaceResourceEdits: IWorkspaceEditDto;
setup(() => {
workspaceResourceEdits = null!;
let rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(MainContext.MainThreadBulkEdits, new class extends mock<MainThreadBulkEditsShape>() {
$tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise<boolean> {
workspaceResourceEdits = _workspaceResourceEdits;
return Promise.resolve(true);
}
});
const documentsAndEditors = new ExtHostDocumentsAndEditors(SingleProxyRPCProtocol(null), new NullLogService());
documentsAndEditors.$acceptDocumentsAndEditorsDelta({
addedDocuments: [{
isDirty: false,
modeId: 'foo',
uri: resource,
versionId: 1337,
lines: ['foo'],
EOL: '\n',
}]
});
bulkEdits = new ExtHostBulkEdits(rpcProtocol, documentsAndEditors, null!);
});
test('uses version id if document available', async () => {
let edit = new extHostTypes.WorkspaceEdit();
edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello');
await bulkEdits.applyWorkspaceEdit(edit);
assert.equal(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
assertType(first._type === WorkspaceEditType.Text);
assert.equal(first.modelVersionId, 1337);
});
test('does not use version id if document is not available', async () => {
let edit = new extHostTypes.WorkspaceEdit();
edit.replace(URI.parse('foo:bar2'), new extHostTypes.Range(0, 0, 0, 0), 'hello');
await bulkEdits.applyWorkspaceEdit(edit);
assert.equal(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
assertType(first._type === WorkspaceEditType.Text);
assert.ok(typeof first.modelVersionId === 'undefined');
});
});

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* 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 { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { MainThreadCommandsShape } from 'vs/workbench/api/common/extHost.protocol';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
import { mock } from 'vs/base/test/common/mock';
import { NullLogService } from 'vs/platform/log/common/log';
suite('ExtHostCommands', function () {
test('dispose calls unregister', function () {
let lastUnregister: string;
const shape = new class extends mock<MainThreadCommandsShape>() {
$registerCommand(id: string): void {
//
}
$unregisterCommand(id: string): void {
lastUnregister = id;
}
};
const commands = new ExtHostCommands(
SingleProxyRPCProtocol(shape),
new NullLogService()
);
commands.registerCommand(true, 'foo', (): any => { }).dispose();
assert.equal(lastUnregister!, 'foo');
assert.equal(CommandsRegistry.getCommand('foo'), undefined);
});
test('dispose bubbles only once', function () {
let unregisterCounter = 0;
const shape = new class extends mock<MainThreadCommandsShape>() {
$registerCommand(id: string): void {
//
}
$unregisterCommand(id: string): void {
unregisterCounter += 1;
}
};
const commands = new ExtHostCommands(
SingleProxyRPCProtocol(shape),
new NullLogService()
);
const reg = commands.registerCommand(true, 'foo', (): any => { });
reg.dispose();
reg.dispose();
reg.dispose();
assert.equal(unregisterCounter, 1);
});
test('execute with retry', async function () {
let count = 0;
const shape = new class extends mock<MainThreadCommandsShape>() {
$registerCommand(id: string): void {
//
}
async $executeCommand<T>(id: string, args: any[], retry: boolean): Promise<T | undefined> {
count++;
assert.equal(retry, count === 1);
if (count === 1) {
assert.equal(retry, true);
throw new Error('$executeCommand:retry');
} else {
assert.equal(retry, false);
return <any>17;
}
}
};
const commands = new ExtHostCommands(
SingleProxyRPCProtocol(shape),
new NullLogService()
);
const result = await commands.executeCommand('fooo', [this, true]);
assert.equal(result, 17);
assert.equal(count, 2);
});
});

View File

@@ -0,0 +1,735 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI, UriComponents } from 'vs/base/common/uri';
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { MainThreadConfigurationShape, IConfigurationInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { TestRPCProtocol } from './testRPCProtocol';
import { mock } from 'vs/base/test/common/mock';
import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationModel, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { NullLogService } from 'vs/platform/log/common/log';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
suite('ExtHostConfiguration', function () {
class RecordingShape extends mock<MainThreadConfigurationShape>() {
lastArgs!: [ConfigurationTarget, string, any];
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise<void> {
this.lastArgs = [target, key, value];
return Promise.resolve(undefined);
}
}
function createExtHostWorkspace(): ExtHostWorkspace {
return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock<IExtHostInitDataService>() { }, new NullLogService());
}
function createExtHostConfiguration(contents: any = Object.create(null), shape?: MainThreadConfigurationShape) {
if (!shape) {
shape = new class extends mock<MainThreadConfigurationShape>() { };
}
return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents), new NullLogService());
}
function createConfigurationData(contents: any): IConfigurationInitData {
return {
defaults: new ConfigurationModel(contents),
user: new ConfigurationModel(contents),
workspace: new ConfigurationModel(),
folders: [],
configurationScopes: []
};
}
test('getConfiguration fails regression test 1.7.1 -> 1.8 #15552', function () {
const extHostConfig = createExtHostConfiguration({
'search': {
'exclude': {
'**/node_modules': true
}
}
});
assert.equal(extHostConfig.getConfiguration('search.exclude')['**/node_modules'], true);
assert.equal(extHostConfig.getConfiguration('search.exclude').get('**/node_modules'), true);
assert.equal(extHostConfig.getConfiguration('search').get<any>('exclude')['**/node_modules'], true);
assert.equal(extHostConfig.getConfiguration('search.exclude').has('**/node_modules'), true);
assert.equal(extHostConfig.getConfiguration('search').has('exclude.**/node_modules'), true);
});
test('has/get', () => {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
}
});
const config = all.getConfiguration('farboo');
assert.ok(config.has('config0'));
assert.equal(config.get('config0'), true);
assert.equal(config.get('config4'), '');
assert.equal(config['config0'], true);
assert.equal(config['config4'], '');
assert.ok(config.has('nested.config1'));
assert.equal(config.get('nested.config1'), 42);
assert.ok(config.has('nested.config2'));
assert.equal(config.get('nested.config2'), 'Das Pferd frisst kein Reis.');
assert.ok(config.has('nested'));
assert.deepEqual(config.get('nested'), { config1: 42, config2: 'Das Pferd frisst kein Reis.' });
});
test('can modify the returned configuration', function () {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
},
'workbench': {
'colorCustomizations': {
'statusBar.foreground': 'somevalue'
}
}
});
let testObject = all.getConfiguration();
let actual = testObject.get<any>('farboo')!;
actual['nested']['config1'] = 41;
assert.equal(41, actual['nested']['config1']);
actual['farboo1'] = 'newValue';
assert.equal('newValue', actual['farboo1']);
testObject = all.getConfiguration();
actual = testObject.get('farboo')!;
assert.equal(actual['nested']['config1'], 42);
assert.equal(actual['farboo1'], undefined);
testObject = all.getConfiguration();
actual = testObject.get('farboo')!;
assert.equal(actual['config0'], true);
actual['config0'] = false;
assert.equal(actual['config0'], false);
testObject = all.getConfiguration();
actual = testObject.get('farboo')!;
assert.equal(actual['config0'], true);
testObject = all.getConfiguration();
actual = testObject.inspect('farboo')!;
actual['value'] = 'effectiveValue';
assert.equal('effectiveValue', actual['value']);
testObject = all.getConfiguration('workbench');
actual = testObject.get('colorCustomizations')!;
actual['statusBar.foreground'] = undefined;
assert.equal(actual['statusBar.foreground'], undefined);
testObject = all.getConfiguration('workbench');
actual = testObject.get('colorCustomizations')!;
assert.equal(actual['statusBar.foreground'], 'somevalue');
});
test('Stringify returned configuration', function () {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
},
'workbench': {
'colorCustomizations': {
'statusBar.foreground': 'somevalue'
},
'emptyobjectkey': {
}
}
});
const testObject = all.getConfiguration();
let actual: any = testObject.get('farboo');
assert.deepEqual(JSON.stringify({
'config0': true,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
}), JSON.stringify(actual));
assert.deepEqual(undefined, JSON.stringify(testObject.get('unknownkey')));
actual = testObject.get('farboo')!;
actual['config0'] = false;
assert.deepEqual(JSON.stringify({
'config0': false,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
}), JSON.stringify(actual));
actual = testObject.get<any>('workbench')!['colorCustomizations']!;
actual['statusBar.background'] = 'anothervalue';
assert.deepEqual(JSON.stringify({
'statusBar.foreground': 'somevalue',
'statusBar.background': 'anothervalue'
}), JSON.stringify(actual));
actual = testObject.get('workbench');
actual['unknownkey'] = 'somevalue';
assert.deepEqual(JSON.stringify({
'colorCustomizations': {
'statusBar.foreground': 'somevalue'
},
'emptyobjectkey': {},
'unknownkey': 'somevalue'
}), JSON.stringify(actual));
actual = all.getConfiguration('workbench').get('emptyobjectkey');
actual = {
...(actual || {}),
'statusBar.background': `#0ff`,
'statusBar.foreground': `#ff0`,
};
assert.deepEqual(JSON.stringify({
'statusBar.background': `#0ff`,
'statusBar.foreground': `#ff0`,
}), JSON.stringify(actual));
actual = all.getConfiguration('workbench').get('unknownkey');
actual = {
...(actual || {}),
'statusBar.background': `#0ff`,
'statusBar.foreground': `#ff0`,
};
assert.deepEqual(JSON.stringify({
'statusBar.background': `#0ff`,
'statusBar.foreground': `#ff0`,
}), JSON.stringify(actual));
});
test('cannot modify returned configuration', function () {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'nested': {
'config1': 42,
'config2': 'Das Pferd frisst kein Reis.'
},
'config4': ''
}
});
let testObject: any = all.getConfiguration();
try {
testObject['get'] = null;
assert.fail('This should be readonly');
} catch (e) {
}
try {
testObject['farboo']['config0'] = false;
assert.fail('This should be readonly');
} catch (e) {
}
try {
testObject['farboo']['farboo1'] = 'hello';
assert.fail('This should be readonly');
} catch (e) {
}
});
test('inspect in no workspace context', function () {
const testObject = new ExtHostConfigProvider(
new class extends mock<MainThreadConfigurationShape>() { },
createExtHostWorkspace(),
{
defaults: new ConfigurationModel({
'editor': {
'wordWrap': 'off'
}
}, ['editor.wordWrap']),
user: new ConfigurationModel({
'editor': {
'wordWrap': 'on'
}
}, ['editor.wordWrap']),
workspace: new ConfigurationModel({}, []),
folders: [],
configurationScopes: []
},
new NullLogService()
);
let actual = testObject.getConfiguration().inspect('editor.wordWrap')!;
assert.equal(actual.defaultValue, 'off');
assert.equal(actual.globalValue, 'on');
assert.equal(actual.workspaceValue, undefined);
assert.equal(actual.workspaceFolderValue, undefined);
actual = testObject.getConfiguration('editor').inspect('wordWrap')!;
assert.equal(actual.defaultValue, 'off');
assert.equal(actual.globalValue, 'on');
assert.equal(actual.workspaceValue, undefined);
assert.equal(actual.workspaceFolderValue, undefined);
});
test('inspect in single root context', function () {
const workspaceUri = URI.file('foo');
const folders: [UriComponents, IConfigurationModel][] = [];
const workspace = new ConfigurationModel({
'editor': {
'wordWrap': 'bounded'
}
}, ['editor.wordWrap']);
folders.push([workspaceUri, workspace]);
const extHostWorkspace = createExtHostWorkspace();
extHostWorkspace.$initializeWorkspace({
'id': 'foo',
'folders': [aWorkspaceFolder(URI.file('foo'), 0)],
'name': 'foo'
});
const testObject = new ExtHostConfigProvider(
new class extends mock<MainThreadConfigurationShape>() { },
extHostWorkspace,
{
defaults: new ConfigurationModel({
'editor': {
'wordWrap': 'off'
}
}, ['editor.wordWrap']),
user: new ConfigurationModel({
'editor': {
'wordWrap': 'on'
}
}, ['editor.wordWrap']),
workspace,
folders,
configurationScopes: []
},
new NullLogService()
);
let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!;
assert.equal(actual1.defaultValue, 'off');
assert.equal(actual1.globalValue, 'on');
assert.equal(actual1.workspaceValue, 'bounded');
assert.equal(actual1.workspaceFolderValue, undefined);
actual1 = testObject.getConfiguration('editor').inspect('wordWrap')!;
assert.equal(actual1.defaultValue, 'off');
assert.equal(actual1.globalValue, 'on');
assert.equal(actual1.workspaceValue, 'bounded');
assert.equal(actual1.workspaceFolderValue, undefined);
let actual2 = testObject.getConfiguration(undefined, workspaceUri).inspect('editor.wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'bounded');
actual2 = testObject.getConfiguration('editor', workspaceUri).inspect('wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'bounded');
});
test('inspect in multi root context', function () {
const workspace = new ConfigurationModel({
'editor': {
'wordWrap': 'bounded'
}
}, ['editor.wordWrap']);
const firstRoot = URI.file('foo1');
const secondRoot = URI.file('foo2');
const thirdRoot = URI.file('foo3');
const folders: [UriComponents, IConfigurationModel][] = [];
folders.push([firstRoot, new ConfigurationModel({
'editor': {
'wordWrap': 'off',
'lineNumbers': 'relative'
}
}, ['editor.wordWrap'])]);
folders.push([secondRoot, new ConfigurationModel({
'editor': {
'wordWrap': 'on'
}
}, ['editor.wordWrap'])]);
folders.push([thirdRoot, new ConfigurationModel({}, [])]);
const extHostWorkspace = createExtHostWorkspace();
extHostWorkspace.$initializeWorkspace({
'id': 'foo',
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
'name': 'foo'
});
const testObject = new ExtHostConfigProvider(
new class extends mock<MainThreadConfigurationShape>() { },
extHostWorkspace,
{
defaults: new ConfigurationModel({
'editor': {
'wordWrap': 'off',
'lineNumbers': 'on'
}
}, ['editor.wordWrap']),
user: new ConfigurationModel({
'editor': {
'wordWrap': 'on'
}
}, ['editor.wordWrap']),
workspace,
folders,
configurationScopes: []
},
new NullLogService()
);
let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!;
assert.equal(actual1.defaultValue, 'off');
assert.equal(actual1.globalValue, 'on');
assert.equal(actual1.workspaceValue, 'bounded');
assert.equal(actual1.workspaceFolderValue, undefined);
actual1 = testObject.getConfiguration('editor').inspect('wordWrap')!;
assert.equal(actual1.defaultValue, 'off');
assert.equal(actual1.globalValue, 'on');
assert.equal(actual1.workspaceValue, 'bounded');
assert.equal(actual1.workspaceFolderValue, undefined);
actual1 = testObject.getConfiguration('editor').inspect('lineNumbers')!;
assert.equal(actual1.defaultValue, 'on');
assert.equal(actual1.globalValue, undefined);
assert.equal(actual1.workspaceValue, undefined);
assert.equal(actual1.workspaceFolderValue, undefined);
let actual2 = testObject.getConfiguration(undefined, firstRoot).inspect('editor.wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'off');
actual2 = testObject.getConfiguration('editor', firstRoot).inspect('wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'off');
actual2 = testObject.getConfiguration('editor', firstRoot).inspect('lineNumbers')!;
assert.equal(actual2.defaultValue, 'on');
assert.equal(actual2.globalValue, undefined);
assert.equal(actual2.workspaceValue, undefined);
assert.equal(actual2.workspaceFolderValue, 'relative');
actual2 = testObject.getConfiguration(undefined, secondRoot).inspect('editor.wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'on');
actual2 = testObject.getConfiguration('editor', secondRoot).inspect('wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.equal(actual2.workspaceFolderValue, 'on');
actual2 = testObject.getConfiguration(undefined, thirdRoot).inspect('editor.wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.ok(Object.keys(actual2).indexOf('workspaceFolderValue') !== -1);
assert.equal(actual2.workspaceFolderValue, undefined);
actual2 = testObject.getConfiguration('editor', thirdRoot).inspect('wordWrap')!;
assert.equal(actual2.defaultValue, 'off');
assert.equal(actual2.globalValue, 'on');
assert.equal(actual2.workspaceValue, 'bounded');
assert.ok(Object.keys(actual2).indexOf('workspaceFolderValue') !== -1);
assert.equal(actual2.workspaceFolderValue, undefined);
});
test('inspect with language overrides', function () {
const firstRoot = URI.file('foo1');
const secondRoot = URI.file('foo2');
const folders: [UriComponents, IConfigurationModel][] = [];
folders.push([firstRoot, toConfigurationModel({
'editor.wordWrap': 'bounded',
'[typescript]': {
'editor.wordWrap': 'unbounded',
}
})]);
folders.push([secondRoot, toConfigurationModel({})]);
const extHostWorkspace = createExtHostWorkspace();
extHostWorkspace.$initializeWorkspace({
'id': 'foo',
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
'name': 'foo'
});
const testObject = new ExtHostConfigProvider(
new class extends mock<MainThreadConfigurationShape>() { },
extHostWorkspace,
{
defaults: toConfigurationModel({
'editor.wordWrap': 'off',
'[markdown]': {
'editor.wordWrap': 'bounded',
}
}),
user: toConfigurationModel({
'editor.wordWrap': 'bounded',
'[typescript]': {
'editor.lineNumbers': 'off',
}
}),
workspace: toConfigurationModel({
'[typescript]': {
'editor.wordWrap': 'unbounded',
'editor.lineNumbers': 'off',
}
}),
folders,
configurationScopes: []
},
new NullLogService()
);
let actual = testObject.getConfiguration(undefined, { uri: firstRoot, languageId: 'typescript' }).inspect('editor.wordWrap')!;
assert.equal(actual.defaultValue, 'off');
assert.equal(actual.globalValue, 'bounded');
assert.equal(actual.workspaceValue, undefined);
assert.equal(actual.workspaceFolderValue, 'bounded');
assert.equal(actual.defaultLanguageValue, undefined);
assert.equal(actual.globalLanguageValue, undefined);
assert.equal(actual.workspaceLanguageValue, 'unbounded');
assert.equal(actual.workspaceFolderLanguageValue, 'unbounded');
assert.deepEqual(actual.languageIds, ['markdown', 'typescript']);
actual = testObject.getConfiguration(undefined, { uri: secondRoot, languageId: 'typescript' }).inspect('editor.wordWrap')!;
assert.equal(actual.defaultValue, 'off');
assert.equal(actual.globalValue, 'bounded');
assert.equal(actual.workspaceValue, undefined);
assert.equal(actual.workspaceFolderValue, undefined);
assert.equal(actual.defaultLanguageValue, undefined);
assert.equal(actual.globalLanguageValue, undefined);
assert.equal(actual.workspaceLanguageValue, 'unbounded');
assert.equal(actual.workspaceFolderLanguageValue, undefined);
assert.deepEqual(actual.languageIds, ['markdown', 'typescript']);
});
test('getConfiguration vs get', function () {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'config4': 38
}
});
let config = all.getConfiguration('farboo.config0');
assert.equal(config.get(''), undefined);
assert.equal(config.has(''), false);
config = all.getConfiguration('farboo');
assert.equal(config.get('config0'), true);
assert.equal(config.has('config0'), true);
});
test('getConfiguration vs get', function () {
const all = createExtHostConfiguration({
'farboo': {
'config0': true,
'config4': 38
}
});
let config = all.getConfiguration('farboo.config0');
assert.equal(config.get(''), undefined);
assert.equal(config.has(''), false);
config = all.getConfiguration('farboo');
assert.equal(config.get('config0'), true);
assert.equal(config.has('config0'), true);
});
test('name vs property', function () {
const all = createExtHostConfiguration({
'farboo': {
'get': 'get-prop'
}
});
const config = all.getConfiguration('farboo');
assert.ok(config.has('get'));
assert.equal(config.get('get'), 'get-prop');
assert.deepEqual(config['get'], config.get);
assert.throws(() => config['get'] = <any>'get-prop');
});
test('update: no target passes null', function () {
const shape = new RecordingShape();
const allConfig = createExtHostConfiguration({
'foo': {
'bar': 1,
'far': 1
}
}, shape);
let config = allConfig.getConfiguration('foo');
config.update('bar', 42);
assert.equal(shape.lastArgs[0], null);
});
test('update/section to key', function () {
const shape = new RecordingShape();
const allConfig = createExtHostConfiguration({
'foo': {
'bar': 1,
'far': 1
}
}, shape);
let config = allConfig.getConfiguration('foo');
config.update('bar', 42, true);
assert.equal(shape.lastArgs[0], ConfigurationTarget.USER);
assert.equal(shape.lastArgs[1], 'foo.bar');
assert.equal(shape.lastArgs[2], 42);
config = allConfig.getConfiguration('');
config.update('bar', 42, true);
assert.equal(shape.lastArgs[1], 'bar');
config.update('foo.bar', 42, true);
assert.equal(shape.lastArgs[1], 'foo.bar');
});
test('update, what is #15834', function () {
const shape = new RecordingShape();
const allConfig = createExtHostConfiguration({
'editor': {
'formatOnSave': true
}
}, shape);
allConfig.getConfiguration('editor').update('formatOnSave', { extensions: ['ts'] });
assert.equal(shape.lastArgs[1], 'editor.formatOnSave');
assert.deepEqual(shape.lastArgs[2], { extensions: ['ts'] });
});
test('update/error-state not OK', function () {
const shape = new class extends mock<MainThreadConfigurationShape>() {
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise<any> {
return Promise.reject(new Error('Unknown Key')); // something !== OK
}
};
return createExtHostConfiguration({}, shape)
.getConfiguration('')
.update('', true, false)
.then(() => assert.ok(false), err => { /* expecting rejection */ });
});
test('configuration change event', (done) => {
const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0);
const extHostWorkspace = createExtHostWorkspace();
extHostWorkspace.$initializeWorkspace({
'id': 'foo',
'folders': [workspaceFolder],
'name': 'foo'
});
const testObject = new ExtHostConfigProvider(
new class extends mock<MainThreadConfigurationShape>() { },
extHostWorkspace,
createConfigurationData({
'farboo': {
'config': false,
'updatedConfig': false
}
}),
new NullLogService()
);
const newConfigData = createConfigurationData({
'farboo': {
'config': false,
'updatedConfig': true,
'newConfig': true,
}
});
const configEventData: IConfigurationChange = { keys: ['farboo.updatedConfig', 'farboo.newConfig'], overrides: [] };
testObject.onDidChangeConfiguration(e => {
assert.deepEqual(testObject.getConfiguration().get('farboo'), {
'config': false,
'updatedConfig': true,
'newConfig': true,
});
assert.ok(e.affectsConfiguration('farboo'));
assert.ok(e.affectsConfiguration('farboo', workspaceFolder.uri));
assert.ok(e.affectsConfiguration('farboo', URI.file('any')));
assert.ok(e.affectsConfiguration('farboo.updatedConfig'));
assert.ok(e.affectsConfiguration('farboo.updatedConfig', workspaceFolder.uri));
assert.ok(e.affectsConfiguration('farboo.updatedConfig', URI.file('any')));
assert.ok(e.affectsConfiguration('farboo.newConfig'));
assert.ok(e.affectsConfiguration('farboo.newConfig', workspaceFolder.uri));
assert.ok(e.affectsConfiguration('farboo.newConfig', URI.file('any')));
assert.ok(!e.affectsConfiguration('farboo.config'));
assert.ok(!e.affectsConfiguration('farboo.config', workspaceFolder.uri));
assert.ok(!e.affectsConfiguration('farboo.config', URI.file('any')));
done();
});
testObject.$acceptConfigurationChanged(newConfigData, configEventData);
});
function aWorkspaceFolder(uri: URI, index: number, name: string = ''): IWorkspaceFolder {
return new WorkspaceFolder({ uri, name, index });
}
function toConfigurationModel(obj: any): ConfigurationModel {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify(obj));
return parser.configurationModel;
}
});

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* 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 { timeout } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { NullLogService } from 'vs/platform/log/common/log';
import { MainThreadDecorationsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
suite('ExtHostDecorations', function () {
let mainThreadShape: MainThreadDecorationsShape;
let extHostDecorations: ExtHostDecorations;
let providers = new Set<number>();
setup(function () {
providers.clear();
mainThreadShape = new class extends mock<MainThreadDecorationsShape>() {
$registerDecorationProvider(handle: number) {
providers.add(handle);
}
};
extHostDecorations = new ExtHostDecorations(
new class extends mock<IExtHostRpcService>() {
getProxy(): any {
return mainThreadShape;
}
},
new NullLogService()
);
});
test('SCM Decorations missing #100524', async function () {
let calledA = false;
let calledB = false;
// never returns
extHostDecorations.registerDecorationProvider({
onDidChange: Event.None,
provideFileDecoration() {
calledA = true;
return new Promise(() => { });
}
}, nullExtensionDescription.identifier);
// always returns
extHostDecorations.registerDecorationProvider({
onDidChange: Event.None,
provideFileDecoration() {
calledB = true;
return new Promise(resolve => resolve({ badge: 'H', tooltip: 'Hello' }));
}
}, nullExtensionDescription.identifier);
const requests = [...providers.values()].map((handle, idx) => {
return extHostDecorations.$provideDecorations(handle, [{ id: idx, uri: URI.parse('test:///file') }], CancellationToken.None);
});
assert.equal(calledA, true);
assert.equal(calledB, true);
assert.equal(requests.length, 2);
const [first, second] = requests;
const firstResult = await Promise.race([first, timeout(30).then(() => false)]);
assert.equal(typeof firstResult, 'boolean'); // never finishes...
const secondResult = await Promise.race([second, timeout(30).then(() => false)]);
assert.equal(typeof secondResult, 'object');
});
});

View File

@@ -0,0 +1,471 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI, UriComponents } from 'vs/base/common/uri';
import { DiagnosticCollection, ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
import { Diagnostic, DiagnosticSeverity, Range, DiagnosticRelatedInformation, Location } from 'vs/workbench/api/common/extHostTypes';
import { MainThreadDiagnosticsShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol';
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { mock } from 'vs/base/test/common/mock';
import { Emitter, Event } from 'vs/base/common/event';
import { NullLogService } from 'vs/platform/log/common/log';
import type * as vscode from 'vscode';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
suite('ExtHostDiagnostics', () => {
class DiagnosticsShape extends mock<MainThreadDiagnosticsShape>() {
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void {
//
}
$clear(owner: string): void {
//
}
}
test('disposeCheck', () => {
const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
collection.dispose();
collection.dispose(); // that's OK
assert.throws(() => collection.name);
assert.throws(() => collection.clear());
assert.throws(() => collection.delete(URI.parse('aa:bb')));
assert.throws(() => collection.forEach(() => { }));
assert.throws(() => collection.get(URI.parse('aa:bb')));
assert.throws(() => collection.has(URI.parse('aa:bb')));
assert.throws(() => collection.set(URI.parse('aa:bb'), []));
assert.throws(() => collection.set(URI.parse('aa:bb'), undefined!));
});
test('diagnostic collection, forEach, clear, has', function () {
let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
assert.equal(collection.name, 'test');
collection.dispose();
assert.throws(() => collection.name);
let c = 0;
collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
collection.forEach(() => c++);
assert.equal(c, 0);
collection.set(URI.parse('foo:bar'), [
new Diagnostic(new Range(0, 0, 1, 1), 'message-1'),
new Diagnostic(new Range(0, 0, 1, 1), 'message-2')
]);
collection.forEach(() => c++);
assert.equal(c, 1);
c = 0;
collection.clear();
collection.forEach(() => c++);
assert.equal(c, 0);
collection.set(URI.parse('foo:bar1'), [
new Diagnostic(new Range(0, 0, 1, 1), 'message-1'),
new Diagnostic(new Range(0, 0, 1, 1), 'message-2')
]);
collection.set(URI.parse('foo:bar2'), [
new Diagnostic(new Range(0, 0, 1, 1), 'message-1'),
new Diagnostic(new Range(0, 0, 1, 1), 'message-2')
]);
collection.forEach(() => c++);
assert.equal(c, 2);
assert.ok(collection.has(URI.parse('foo:bar1')));
assert.ok(collection.has(URI.parse('foo:bar2')));
assert.ok(!collection.has(URI.parse('foo:bar3')));
collection.delete(URI.parse('foo:bar1'));
assert.ok(!collection.has(URI.parse('foo:bar1')));
collection.dispose();
});
test('diagnostic collection, immutable read', function () {
let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
collection.set(URI.parse('foo:bar'), [
new Diagnostic(new Range(0, 0, 1, 1), 'message-1'),
new Diagnostic(new Range(0, 0, 1, 1), 'message-2')
]);
let array = collection.get(URI.parse('foo:bar')) as Diagnostic[];
assert.throws(() => array.length = 0);
assert.throws(() => array.pop());
assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil'));
collection.forEach((uri: URI, array: readonly vscode.Diagnostic[]): any => {
assert.throws(() => (array as Diagnostic[]).length = 0);
assert.throws(() => (array as Diagnostic[]).pop());
assert.throws(() => (array as Diagnostic[])[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil'));
});
array = collection.get(URI.parse('foo:bar')) as Diagnostic[];
assert.equal(array.length, 2);
collection.dispose();
});
test('diagnostics collection, set with dupliclated tuples', function () {
let collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
let uri = URI.parse('sc:hightower');
collection.set([
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-1')]],
[URI.parse('some:thing'), [new Diagnostic(new Range(0, 0, 1, 1), 'something')]],
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-2')]],
]);
let array = collection.get(uri);
assert.equal(array.length, 2);
let [first, second] = array;
assert.equal(first.message, 'message-1');
assert.equal(second.message, 'message-2');
// clear
collection.delete(uri);
assert.ok(!collection.has(uri));
// bad tuple clears 1/2
collection.set([
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-1')]],
[URI.parse('some:thing'), [new Diagnostic(new Range(0, 0, 1, 1), 'something')]],
[uri, undefined!]
]);
assert.ok(!collection.has(uri));
// clear
collection.delete(uri);
assert.ok(!collection.has(uri));
// bad tuple clears 2/2
collection.set([
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-1')]],
[URI.parse('some:thing'), [new Diagnostic(new Range(0, 0, 1, 1), 'something')]],
[uri, undefined!],
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-2')]],
[uri, [new Diagnostic(new Range(0, 0, 0, 1), 'message-3')]],
]);
array = collection.get(uri);
assert.equal(array.length, 2);
[first, second] = array;
assert.equal(first.message, 'message-2');
assert.equal(second.message, 'message-3');
collection.dispose();
});
test('diagnostics collection, set tuple overrides, #11547', function () {
let lastEntries!: [UriComponents, IMarkerData[]][];
let collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape {
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void {
lastEntries = entries;
return super.$changeMany(owner, entries);
}
}, new Emitter());
let uri = URI.parse('sc:hightower');
collection.set([[uri, [new Diagnostic(new Range(0, 0, 1, 1), 'error')]]]);
assert.equal(collection.get(uri).length, 1);
assert.equal(collection.get(uri)[0].message, 'error');
assert.equal(lastEntries.length, 1);
let [[, data1]] = lastEntries;
assert.equal(data1.length, 1);
assert.equal(data1[0].message, 'error');
lastEntries = undefined!;
collection.set([[uri, [new Diagnostic(new Range(0, 0, 1, 1), 'warning')]]]);
assert.equal(collection.get(uri).length, 1);
assert.equal(collection.get(uri)[0].message, 'warning');
assert.equal(lastEntries.length, 1);
let [[, data2]] = lastEntries;
assert.equal(data2.length, 1);
assert.equal(data2[0].message, 'warning');
lastEntries = undefined!;
});
test('do send message when not making a change', function () {
let changeCount = 0;
let eventCount = 0;
const emitter = new Emitter<any>();
emitter.event(_ => eventCount += 1);
const collection = new DiagnosticCollection('test', 'test', 100, new class extends DiagnosticsShape {
$changeMany() {
changeCount += 1;
}
}, emitter);
let uri = URI.parse('sc:hightower');
let diag = new Diagnostic(new Range(0, 0, 0, 1), 'ffff');
collection.set(uri, [diag]);
assert.equal(changeCount, 1);
assert.equal(eventCount, 1);
collection.set(uri, [diag]);
assert.equal(changeCount, 2);
assert.equal(eventCount, 2);
});
test('diagnostics collection, tuples and undefined (small array), #15585', function () {
const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
let uri = URI.parse('sc:hightower');
let uri2 = URI.parse('sc:nomad');
let diag = new Diagnostic(new Range(0, 0, 0, 1), 'ffff');
collection.set([
[uri, [diag, diag, diag]],
[uri, undefined!],
[uri, [diag]],
[uri2, [diag, diag]],
[uri2, undefined!],
[uri2, [diag]],
]);
assert.equal(collection.get(uri).length, 1);
assert.equal(collection.get(uri2).length, 1);
});
test('diagnostics collection, tuples and undefined (large array), #15585', function () {
const collection = new DiagnosticCollection('test', 'test', 100, new DiagnosticsShape(), new Emitter());
const tuples: [URI, Diagnostic[]][] = [];
for (let i = 0; i < 500; i++) {
let uri = URI.parse('sc:hightower#' + i);
let diag = new Diagnostic(new Range(0, 0, 0, 1), i.toString());
tuples.push([uri, [diag, diag, diag]]);
tuples.push([uri, undefined!]);
tuples.push([uri, [diag]]);
}
collection.set(tuples);
for (let i = 0; i < 500; i++) {
let uri = URI.parse('sc:hightower#' + i);
assert.equal(collection.has(uri), true);
assert.equal(collection.get(uri).length, 1);
}
});
test('diagnostic capping', function () {
let lastEntries!: [UriComponents, IMarkerData[]][];
let collection = new DiagnosticCollection('test', 'test', 250, new class extends DiagnosticsShape {
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void {
lastEntries = entries;
return super.$changeMany(owner, entries);
}
}, new Emitter());
let uri = URI.parse('aa:bb');
let diagnostics: Diagnostic[] = [];
for (let i = 0; i < 500; i++) {
diagnostics.push(new Diagnostic(new Range(i, 0, i + 1, 0), `error#${i}`, i < 300
? DiagnosticSeverity.Warning
: DiagnosticSeverity.Error));
}
collection.set(uri, diagnostics);
assert.equal(collection.get(uri).length, 500);
assert.equal(lastEntries.length, 1);
assert.equal(lastEntries[0][1].length, 251);
assert.equal(lastEntries[0][1][0].severity, MarkerSeverity.Error);
assert.equal(lastEntries[0][1][200].severity, MarkerSeverity.Warning);
assert.equal(lastEntries[0][1][250].severity, MarkerSeverity.Info);
});
test('diagnostic eventing', async function () {
let emitter = new Emitter<Array<URI>>();
let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter);
let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1');
let diag2 = new Diagnostic(new Range(1, 1, 2, 3), 'diag2');
let diag3 = new Diagnostic(new Range(1, 1, 2, 3), 'diag3');
let p = Event.toPromise(emitter.event).then(a => {
assert.equal(a.length, 1);
assert.equal(a[0].toString(), 'aa:bb');
assert.ok(URI.isUri(a[0]));
});
collection.set(URI.parse('aa:bb'), []);
await p;
p = Event.toPromise(emitter.event).then(e => {
assert.equal(e.length, 2);
assert.ok(URI.isUri(e[0]));
assert.ok(URI.isUri(e[1]));
assert.equal(e[0].toString(), 'aa:bb');
assert.equal(e[1].toString(), 'aa:cc');
});
collection.set([
[URI.parse('aa:bb'), [diag1]],
[URI.parse('aa:cc'), [diag2, diag3]],
]);
await p;
p = Event.toPromise(emitter.event).then(e => {
assert.equal(e.length, 2);
assert.ok(URI.isUri(e[0]));
assert.ok(URI.isUri(e[1]));
});
collection.clear();
await p;
});
test('vscode.languages.onDidChangeDiagnostics Does Not Provide Document URI #49582', async function () {
let emitter = new Emitter<Array<URI>>();
let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter);
let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1');
// delete
collection.set(URI.parse('aa:bb'), [diag1]);
let p = Event.toPromise(emitter.event).then(e => {
assert.equal(e[0].toString(), 'aa:bb');
});
collection.delete(URI.parse('aa:bb'));
await p;
// set->undefined (as delete)
collection.set(URI.parse('aa:bb'), [diag1]);
p = Event.toPromise(emitter.event).then(e => {
assert.equal(e[0].toString(), 'aa:bb');
});
collection.set(URI.parse('aa:bb'), undefined!);
await p;
});
test('diagnostics with related information', function (done) {
let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape {
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) {
let [[, data]] = entries;
assert.equal(entries.length, 1);
assert.equal(data.length, 1);
let [diag] = data;
assert.equal(diag.relatedInformation!.length, 2);
assert.equal(diag.relatedInformation![0].message, 'more1');
assert.equal(diag.relatedInformation![1].message, 'more2');
done();
}
}, new Emitter<any>());
let diag = new Diagnostic(new Range(0, 0, 1, 1), 'Foo');
diag.relatedInformation = [
new DiagnosticRelatedInformation(new Location(URI.parse('cc:dd'), new Range(0, 0, 0, 0)), 'more1'),
new DiagnosticRelatedInformation(new Location(URI.parse('cc:ee'), new Range(0, 0, 0, 0)), 'more2')
];
collection.set(URI.parse('aa:bb'), [diag]);
});
test('vscode.languages.getDiagnostics appears to return old diagnostics in some circumstances #54359', function () {
const ownerHistory: string[] = [];
const diags = new ExtHostDiagnostics(new class implements IMainContext {
getProxy(id: any): any {
return new class DiagnosticsShape {
$clear(owner: string): void {
ownerHistory.push(owner);
}
};
}
set(): any {
return null;
}
assertRegistered(): void {
}
drain() {
return undefined!;
}
}, new NullLogService());
let collection1 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo');
let collection2 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); // warns, uses a different owner
collection1.clear();
collection2.clear();
assert.equal(ownerHistory.length, 2);
assert.equal(ownerHistory[0], 'foo');
assert.equal(ownerHistory[1], 'foo0');
});
test('Error updating diagnostics from extension #60394', function () {
let callCount = 0;
let collection = new DiagnosticCollection('ddd', 'test', 100, new class extends DiagnosticsShape {
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]) {
callCount += 1;
}
}, new Emitter<any>());
let array: Diagnostic[] = [];
let diag1 = new Diagnostic(new Range(0, 0, 1, 1), 'Foo');
let diag2 = new Diagnostic(new Range(0, 0, 1, 1), 'Bar');
array.push(diag1, diag2);
collection.set(URI.parse('test:me'), array);
assert.equal(callCount, 1);
collection.set(URI.parse('test:me'), array);
assert.equal(callCount, 2); // equal array
array.push(diag2);
collection.set(URI.parse('test:me'), array);
assert.equal(callCount, 3); // same but un-equal array
});
test('Diagnostics created by tasks aren\'t accessible to extensions #47292', async function () {
const diags = new ExtHostDiagnostics(new class implements IMainContext {
getProxy(id: any): any {
return {};
}
set(): any {
return null;
}
assertRegistered(): void {
}
drain() {
return undefined!;
}
}, new NullLogService());
//
const uri = URI.parse('foo:bar');
const data: IMarkerData[] = [{
message: 'message',
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
severity: 3
}];
const p1 = Event.toPromise(diags.onDidChangeDiagnostics);
diags.$acceptMarkersChange([[uri, data]]);
await p1;
assert.equal(diags.getDiagnostics(uri).length, 1);
const p2 = Event.toPromise(diags.onDidChangeDiagnostics);
diags.$acceptMarkersChange([[uri, []]]);
await p2;
assert.equal(diags.getDiagnostics(uri).length, 0);
});
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,518 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { Position } from 'vs/workbench/api/common/extHostTypes';
import { Range } from 'vs/editor/common/core/range';
import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import { mock } from 'vs/base/test/common/mock';
import * as perfData from './extHostDocumentData.test.perf-data';
suite('ExtHostDocumentData', () => {
let data: ExtHostDocumentData;
function assertPositionAt(offset: number, line: number, character: number) {
let position = data.document.positionAt(offset);
assert.equal(position.line, line);
assert.equal(position.character, character);
}
function assertOffsetAt(line: number, character: number, offset: number) {
let pos = new Position(line, character);
let actual = data.document.offsetAt(pos);
assert.equal(actual, offset);
}
setup(function () {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
'This is line one', //16
'and this is line number two', //27
'it is followed by #3', //20
'and finished with the fourth.', //29
], '\n', 1, 'text', false);
});
test('readonly-ness', () => {
assert.throws(() => (data as any).document.uri = null);
assert.throws(() => (data as any).document.fileName = 'foofile');
assert.throws(() => (data as any).document.isDirty = false);
assert.throws(() => (data as any).document.isUntitled = false);
assert.throws(() => (data as any).document.languageId = 'dddd');
assert.throws(() => (data as any).document.lineCount = 9);
});
test('save, when disposed', function () {
let saved: URI;
let data = new ExtHostDocumentData(new class extends mock<MainThreadDocumentsShape>() {
$trySaveDocument(uri: URI) {
assert.ok(!saved);
saved = uri;
return Promise.resolve(true);
}
}, URI.parse('foo:bar'), [], '\n', 1, 'text', true);
return data.document.save().then(() => {
assert.equal(saved.toString(), 'foo:bar');
data.dispose();
return data.document.save().then(() => {
assert.ok(false, 'expected failure');
}, err => {
assert.ok(err);
});
});
});
test('read, when disposed', function () {
data.dispose();
const { document } = data;
assert.equal(document.lineCount, 4);
assert.equal(document.lineAt(0).text, 'This is line one');
});
test('lines', () => {
assert.equal(data.document.lineCount, 4);
assert.throws(() => data.document.lineAt(-1));
assert.throws(() => data.document.lineAt(data.document.lineCount));
assert.throws(() => data.document.lineAt(Number.MAX_VALUE));
assert.throws(() => data.document.lineAt(Number.MIN_VALUE));
assert.throws(() => data.document.lineAt(0.8));
let line = data.document.lineAt(0);
assert.equal(line.lineNumber, 0);
assert.equal(line.text.length, 16);
assert.equal(line.text, 'This is line one');
assert.equal(line.isEmptyOrWhitespace, false);
assert.equal(line.firstNonWhitespaceCharacterIndex, 0);
data.onEvents({
changes: [{
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: '\t '
}],
eol: undefined!,
versionId: undefined!,
});
// line didn't change
assert.equal(line.text, 'This is line one');
assert.equal(line.firstNonWhitespaceCharacterIndex, 0);
// fetch line again
line = data.document.lineAt(0);
assert.equal(line.text, '\t This is line one');
assert.equal(line.firstNonWhitespaceCharacterIndex, 2);
});
test('line, issue #5704', function () {
let line = data.document.lineAt(0);
let { range, rangeIncludingLineBreak } = line;
assert.equal(range.end.line, 0);
assert.equal(range.end.character, 16);
assert.equal(rangeIncludingLineBreak.end.line, 1);
assert.equal(rangeIncludingLineBreak.end.character, 0);
line = data.document.lineAt(data.document.lineCount - 1);
range = line.range;
rangeIncludingLineBreak = line.rangeIncludingLineBreak;
assert.equal(range.end.line, 3);
assert.equal(range.end.character, 29);
assert.equal(rangeIncludingLineBreak.end.line, 3);
assert.equal(rangeIncludingLineBreak.end.character, 29);
});
test('offsetAt', () => {
assertOffsetAt(0, 0, 0);
assertOffsetAt(0, 1, 1);
assertOffsetAt(0, 16, 16);
assertOffsetAt(1, 0, 17);
assertOffsetAt(1, 3, 20);
assertOffsetAt(2, 0, 45);
assertOffsetAt(4, 29, 95);
assertOffsetAt(4, 30, 95);
assertOffsetAt(4, Number.MAX_VALUE, 95);
assertOffsetAt(5, 29, 95);
assertOffsetAt(Number.MAX_VALUE, 29, 95);
assertOffsetAt(Number.MAX_VALUE, Number.MAX_VALUE, 95);
});
test('offsetAt, after remove', function () {
data.onEvents({
changes: [{
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: ''
}],
eol: undefined!,
versionId: undefined!,
});
assertOffsetAt(0, 1, 1);
assertOffsetAt(0, 13, 13);
assertOffsetAt(1, 0, 14);
});
test('offsetAt, after replace', function () {
data.onEvents({
changes: [{
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: 'is could be'
}],
eol: undefined!,
versionId: undefined!,
});
assertOffsetAt(0, 1, 1);
assertOffsetAt(0, 24, 24);
assertOffsetAt(1, 0, 25);
});
test('offsetAt, after insert line', function () {
data.onEvents({
changes: [{
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: 'is could be\na line with number'
}],
eol: undefined!,
versionId: undefined!,
});
assertOffsetAt(0, 1, 1);
assertOffsetAt(0, 13, 13);
assertOffsetAt(1, 0, 14);
assertOffsetAt(1, 18, 13 + 1 + 18);
assertOffsetAt(1, 29, 13 + 1 + 29);
assertOffsetAt(2, 0, 13 + 1 + 29 + 1);
});
test('offsetAt, after remove line', function () {
data.onEvents({
changes: [{
range: { startLineNumber: 1, startColumn: 3, endLineNumber: 2, endColumn: 6 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: ''
}],
eol: undefined!,
versionId: undefined!,
});
assertOffsetAt(0, 1, 1);
assertOffsetAt(0, 2, 2);
assertOffsetAt(1, 0, 25);
});
test('positionAt', () => {
assertPositionAt(0, 0, 0);
assertPositionAt(Number.MIN_VALUE, 0, 0);
assertPositionAt(1, 0, 1);
assertPositionAt(16, 0, 16);
assertPositionAt(17, 1, 0);
assertPositionAt(20, 1, 3);
assertPositionAt(45, 2, 0);
assertPositionAt(95, 3, 29);
assertPositionAt(96, 3, 29);
assertPositionAt(99, 3, 29);
assertPositionAt(Number.MAX_VALUE, 3, 29);
});
test('getWordRangeAtPosition', () => {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
'aaaa bbbb+cccc abc'
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 2))!;
assert.equal(range.start.line, 0);
assert.equal(range.start.character, 0);
assert.equal(range.end.line, 0);
assert.equal(range.end.character, 4);
// ignore bad regular expresson /.*/
assert.throws(() => data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!);
range = data.document.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/)!;
assert.equal(range.start.line, 0);
assert.equal(range.start.character, 5);
assert.equal(range.end.line, 0);
assert.equal(range.end.character, 14);
range = data.document.getWordRangeAtPosition(new Position(0, 17), /[a-z+]+/)!;
assert.equal(range.start.line, 0);
assert.equal(range.start.character, 15);
assert.equal(range.end.line, 0);
assert.equal(range.end.character, 18);
range = data.document.getWordRangeAtPosition(new Position(0, 11), /yy/)!;
assert.equal(range, undefined);
});
test('getWordRangeAtPosition doesn\'t quite use the regex as expected, #29102', function () {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
'some text here',
'/** foo bar */',
'function() {',
' "far boo"',
'}'
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 0), /\/\*.+\*\//);
assert.equal(range, undefined);
range = data.document.getWordRangeAtPosition(new Position(1, 0), /\/\*.+\*\//)!;
assert.equal(range.start.line, 1);
assert.equal(range.start.character, 0);
assert.equal(range.end.line, 1);
assert.equal(range.end.character, 14);
range = data.document.getWordRangeAtPosition(new Position(3, 0), /("|').*\1/);
assert.equal(range, undefined);
range = data.document.getWordRangeAtPosition(new Position(3, 1), /("|').*\1/)!;
assert.equal(range.start.line, 3);
assert.equal(range.start.character, 1);
assert.equal(range.end.line, 3);
assert.equal(range.end.character, 10);
});
test('getWordRangeAtPosition can freeze the extension host #95319', function () {
const regex = /(https?:\/\/github\.com\/(([^\s]+)\/([^\s]+))\/([^\s]+\/)?(issues|pull)\/([0-9]+))|(([^\s]+)\/([^\s]+))?#([1-9][0-9]*)($|[\s\:\;\-\(\=])/;
data = new ExtHostDocumentData(undefined!, URI.file(''), [
perfData._$_$_expensive
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 1_177_170), regex)!;
assert.equal(range, undefined);
const pos = new Position(0, 1177170);
range = data.document.getWordRangeAtPosition(pos)!;
assert.ok(range);
assert.ok(range.contains(pos));
assert.equal(data.document.getText(range), 'TaskDefinition');
});
test('Rename popup sometimes populates with text on the left side omitted #96013', function () {
const regex = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
const line = 'int abcdefhijklmnopqwvrstxyz;';
data = new ExtHostDocumentData(undefined!, URI.file(''), [
line
], '\n', 1, 'text', false);
let range = data.document.getWordRangeAtPosition(new Position(0, 27), regex)!;
assert.equal(range.start.line, 0);
assert.equal(range.end.line, 0);
assert.equal(range.start.character, 4);
assert.equal(range.end.character, 28);
});
test('Custom snippet $TM_SELECTED_TEXT not show suggestion #108892', function () {
data = new ExtHostDocumentData(undefined!, URI.file(''), [
` <p><span xml:lang="en">Sheldon</span>, soprannominato "<span xml:lang="en">Shelly</span> dalla madre e dalla sorella, è nato a <span xml:lang="en">Galveston</span>, in <span xml:lang="en">Texas</span>, il 26 febbraio 1980 in un supermercato. È stato un bambino prodigio, come testimoniato dal suo quoziente d'intelligenza (187, di molto superiore alla norma) e dalla sua rapida carriera scolastica: si è diplomato all'eta di 11 anni approdando alla stessa età alla formazione universitaria e all'età di 16 anni ha ottenuto il suo primo dottorato di ricerca. All'inizio della serie e per gran parte di essa vive con il coinquilino Leonard nell'appartamento 4A al 2311 <span xml:lang="en">North Los Robles Avenue</span> di <span xml:lang="en">Pasadena</span>, per poi trasferirsi nell'appartamento di <span xml:lang="en">Penny</span> con <span xml:lang="en">Amy</span> nella decima stagione. Come più volte afferma lui stesso possiede una memoria eidetica e un orecchio assoluto. È stato educato da una madre estremamente religiosa e, in più occasioni, questo aspetto contrasta con il rigore scientifico di <span xml:lang="en">Sheldon</span>; tuttavia la donna sembra essere l'unica persona in grado di comandarlo a bacchetta.</p>`
], '\n', 1, 'text', false);
const pos = new Position(0, 55);
const range = data.document.getWordRangeAtPosition(pos)!;
assert.equal(range.start.line, 0);
assert.equal(range.end.line, 0);
assert.equal(range.start.character, 47);
assert.equal(range.end.character, 61);
assert.equal(data.document.getText(range), 'soprannominato');
});
});
enum AssertDocumentLineMappingDirection {
OffsetToPosition,
PositionToOffset
}
suite('ExtHostDocumentData updates line mapping', () => {
function positionToStr(position: { line: number; character: number; }): string {
return '(' + position.line + ',' + position.character + ')';
}
function assertDocumentLineMapping(doc: ExtHostDocumentData, direction: AssertDocumentLineMappingDirection): void {
let allText = doc.getText();
let line = 0, character = 0, previousIsCarriageReturn = false;
for (let offset = 0; offset <= allText.length; offset++) {
// The position coordinate system cannot express the position between \r and \n
let position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0));
if (direction === AssertDocumentLineMappingDirection.OffsetToPosition) {
let actualPosition = doc.document.positionAt(offset);
assert.equal(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset);
} else {
// The position coordinate system cannot express the position between \r and \n
let expectedOffset = offset + (previousIsCarriageReturn ? -1 : 0);
let actualOffset = doc.document.offsetAt(position);
assert.equal(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position));
}
if (allText.charAt(offset) === '\n') {
line++;
character = 0;
} else {
character++;
}
previousIsCarriageReturn = (allText.charAt(offset) === '\r');
}
}
function createChangeEvent(range: Range, text: string, eol?: string): IModelChangedEvent {
return {
changes: [{
range: range,
rangeOffset: undefined!,
rangeLength: undefined!,
text: text
}],
eol: eol!,
versionId: undefined!,
};
}
function testLineMappingDirectionAfterEvents(lines: string[], eol: string, direction: AssertDocumentLineMappingDirection, e: IModelChangedEvent): void {
let myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 1, 'text', false);
assertDocumentLineMapping(myDocument, direction);
myDocument.onEvents(e);
assertDocumentLineMapping(myDocument, direction);
}
function testLineMappingAfterEvents(lines: string[], e: IModelChangedEvent): void {
testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.PositionToOffset, e);
testLineMappingDirectionAfterEvents(lines, '\n', AssertDocumentLineMappingDirection.OffsetToPosition, e);
testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.PositionToOffset, e);
testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.OffsetToPosition, e);
}
test('line mapping', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], { changes: [], eol: undefined!, versionId: 7 });
});
test('after remove', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 1, 6), ''));
});
test('after replace', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 1, 6), 'is could be'));
});
test('after insert line', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 1, 6), 'is could be\na line with number'));
});
test('after insert two lines', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 1, 6), 'is could be\na line with number\nyet another line'));
});
test('after remove line', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 2, 6), ''));
});
test('after remove two lines', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 3, 6), ''));
});
test('after deleting entire content', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 4, 30), ''));
});
test('after replacing entire content', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 3, 4, 30), 'some new text\nthat\nspans multiple lines'));
});
test('after changing EOL to CRLF', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 1, 1, 1), '', '\r\n'));
});
test('after changing EOL to LF', () => {
testLineMappingAfterEvents([
'This is line one',
'and this is line number two',
'it is followed by #3',
'and finished with the fourth.',
], createChangeEvent(new Range(1, 1, 1, 1), '', '\n'));
});
});

View File

@@ -0,0 +1,396 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/common/extHostTypes';
import { MainThreadTextEditorsShape, IWorkspaceEditDto, IWorkspaceTextEditDto, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
import { SaveReason } from 'vs/workbench/common/editor';
import type * as vscode from 'vscode';
import { mock } from 'vs/base/test/common/mock';
import { NullLogService } from 'vs/platform/log/common/log';
import { timeout } from 'vs/base/common/async';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
suite('ExtHostDocumentSaveParticipant', () => {
let resource = URI.parse('foo:bar');
let mainThreadBulkEdits = new class extends mock<MainThreadBulkEditsShape>() { };
let documents: ExtHostDocuments;
let nullLogService = new NullLogService();
let nullExtensionDescription: IExtensionDescription = {
identifier: new ExtensionIdentifier('nullExtensionDescription'),
name: 'Null Extension Description',
publisher: 'vscode',
enableProposedApi: false,
engines: undefined!,
extensionLocation: undefined!,
isBuiltin: false,
isUserBuiltin: false,
isUnderDevelopment: false,
version: undefined!
};
setup(() => {
const documentsAndEditors = new ExtHostDocumentsAndEditors(SingleProxyRPCProtocol(null), new NullLogService());
documentsAndEditors.$acceptDocumentsAndEditorsDelta({
addedDocuments: [{
isDirty: false,
modeId: 'foo',
uri: resource,
versionId: 1,
lines: ['foo'],
EOL: '\n',
}]
});
documents = new ExtHostDocuments(SingleProxyRPCProtocol(null), documentsAndEditors);
});
test('no listeners, no problem', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => assert.ok(true));
});
test('event delivery', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let event: vscode.TextDocumentWillSaveEvent;
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
event = e;
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
assert.ok(event);
assert.equal(event.reason, TextDocumentSaveReason.Manual);
assert.equal(typeof event.waitUntil, 'function');
});
});
test('event delivery, immutable', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let event: vscode.TextDocumentWillSaveEvent;
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
event = e;
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
assert.ok(event);
assert.throws(() => { (event.document as any) = null!; });
});
});
test('event delivery, bad listener', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
throw new Error('💀');
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(values => {
sub.dispose();
const [first] = values;
assert.equal(first, false);
});
});
test('event delivery, bad listener doesn\'t prevent more events', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
throw new Error('💀');
});
let event: vscode.TextDocumentWillSaveEvent;
let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
event = e;
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub1.dispose();
sub2.dispose();
assert.ok(event);
});
});
test('event delivery, in subscriber order', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let counter = 0;
let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
assert.equal(counter++, 0);
});
let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
assert.equal(counter++, 1);
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub1.dispose();
sub2.dispose();
});
});
test('event delivery, ignore bad listeners', async () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 5, errors: 1 });
let callCount = 0;
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
callCount += 1;
throw new Error('boom');
});
await participant.$participateInSave(resource, SaveReason.EXPLICIT);
await participant.$participateInSave(resource, SaveReason.EXPLICIT);
await participant.$participateInSave(resource, SaveReason.EXPLICIT);
await participant.$participateInSave(resource, SaveReason.EXPLICIT);
sub.dispose();
assert.equal(callCount, 2);
});
test('event delivery, overall timeout', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 20, errors: 5 });
let callCount = 0;
let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
callCount += 1;
event.waitUntil(timeout(1));
});
let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
callCount += 1;
event.waitUntil(timeout(170));
});
let sub3 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
callCount += 1;
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(values => {
sub1.dispose();
sub2.dispose();
sub3.dispose();
assert.equal(callCount, 2);
assert.equal(values.length, 2);
});
});
test('event delivery, waitUntil', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
event.waitUntil(timeout(10));
event.waitUntil(timeout(10));
event.waitUntil(timeout(10));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
});
});
test('event delivery, waitUntil must be called sync', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
event.waitUntil(new Promise<undefined>((resolve, reject) => {
setTimeout(() => {
try {
assert.throws(() => event.waitUntil(timeout(10)));
resolve(undefined);
} catch (e) {
reject(e);
}
}, 10);
}));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
});
});
test('event delivery, waitUntil will timeout', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 5, errors: 3 });
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) {
event.waitUntil(timeout(15));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(values => {
sub.dispose();
const [first] = values;
assert.equal(first, false);
});
});
test('event delivery, waitUntil failure handling', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits);
let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
e.waitUntil(Promise.reject(new Error('dddd')));
});
let event: vscode.TextDocumentWillSaveEvent;
let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
event = e;
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
assert.ok(event);
sub1.dispose();
sub2.dispose();
});
});
test('event delivery, pushEdits sync', () => {
let dto: IWorkspaceEditDto;
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock<MainThreadTextEditorsShape>() {
$tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) {
dto = _edits;
return Promise.resolve(true);
}
});
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
e.waitUntil(Promise.resolve([TextEdit.insert(new Position(0, 0), 'bar')]));
e.waitUntil(Promise.resolve([TextEdit.setEndOfLine(EndOfLine.CRLF)]));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
assert.equal(dto.edits.length, 2);
assert.ok((<IWorkspaceTextEditDto>dto.edits[0]).edit);
assert.ok((<IWorkspaceTextEditDto>dto.edits[1]).edit);
});
});
test('event delivery, concurrent change', () => {
let edits: IWorkspaceEditDto;
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock<MainThreadTextEditorsShape>() {
$tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) {
edits = _edits;
return Promise.resolve(true);
}
});
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
// concurrent change from somewhere
documents.$acceptModelChanged(resource, {
changes: [{
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },
rangeOffset: undefined!,
rangeLength: undefined!,
text: 'bar'
}],
eol: undefined!,
versionId: 2
}, true);
e.waitUntil(Promise.resolve([TextEdit.insert(new Position(0, 0), 'bar')]));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(values => {
sub.dispose();
assert.equal(edits, undefined);
assert.equal(values[0], false);
});
});
test('event delivery, two listeners -> two document states', () => {
const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock<MainThreadTextEditorsShape>() {
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto) {
for (const edit of dto.edits) {
const uri = URI.revive((<IWorkspaceTextEditDto>edit).resource);
const { text, range } = (<IWorkspaceTextEditDto>edit).edit;
documents.$acceptModelChanged(uri, {
changes: [{
range,
text,
rangeOffset: undefined!,
rangeLength: undefined!,
}],
eol: undefined!,
versionId: documents.getDocumentData(uri)!.version + 1
}, true);
// }
}
return Promise.resolve(true);
}
});
const document = documents.getDocument(resource);
let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
// the document state we started with
assert.equal(document.version, 1);
assert.equal(document.getText(), 'foo');
e.waitUntil(Promise.resolve([TextEdit.insert(new Position(0, 0), 'bar')]));
});
let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
// the document state AFTER the first listener kicked in
assert.equal(document.version, 2);
assert.equal(document.getText(), 'barfoo');
e.waitUntil(Promise.resolve([TextEdit.insert(new Position(0, 0), 'bar')]));
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(values => {
sub1.dispose();
sub2.dispose();
// the document state AFTER eventing is done
assert.equal(document.version, 3);
assert.equal(document.getText(), 'barbarfoo');
});
});
test('Log failing listener', function () {
let didLogSomething = false;
let participant = new ExtHostDocumentSaveParticipant(new class extends NullLogService {
error(message: string | Error, ...args: any[]): void {
didLogSomething = true;
}
}, documents, mainThreadBulkEdits);
let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) {
throw new Error('boom');
});
return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => {
sub.dispose();
assert.equal(didLogSomething, true);
});
});
});

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { NullLogService } from 'vs/platform/log/common/log';
suite('ExtHostDocumentsAndEditors', () => {
let editors: ExtHostDocumentsAndEditors;
setup(function () {
editors = new ExtHostDocumentsAndEditors(new TestRPCProtocol(), new NullLogService());
});
test('The value of TextDocument.isClosed is incorrect when a text document is closed, #27949', () => {
editors.$acceptDocumentsAndEditorsDelta({
addedDocuments: [{
EOL: '\n',
isDirty: true,
modeId: 'fooLang',
uri: URI.parse('foo:bar'),
versionId: 1,
lines: [
'first',
'second'
]
}]
});
return new Promise((resolve, reject) => {
editors.onDidRemoveDocuments(e => {
try {
for (const data of e) {
assert.equal(data.document.isClosed, true);
}
resolve(undefined);
} catch (e) {
reject(e);
}
});
editors.$acceptDocumentsAndEditorsDelta({
removedDocuments: [URI.parse('foo:bar')]
});
});
});
});

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* 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 { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService';
import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
import { NullLogService } from 'vs/platform/log/common/log';
suite('ExtHostFileSystemEventService', () => {
test('FileSystemWatcher ignore events properties are reversed #26851', function () {
const protocol: IMainContext = {
getProxy: () => { return undefined!; },
set: undefined!,
assertRegistered: undefined!,
drain: undefined!
};
const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false);
assert.equal(watcher1.ignoreChangeEvents, false);
assert.equal(watcher1.ignoreCreateEvents, false);
assert.equal(watcher1.ignoreDeleteEvents, false);
const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true);
assert.equal(watcher2.ignoreChangeEvents, true);
assert.equal(watcher2.ignoreCreateEvents, true);
assert.equal(watcher2.ignoreDeleteEvents, true);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* 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 { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { mock } from 'vs/base/test/common/mock';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
const emptyDialogService = new class implements IDialogService {
declare readonly _serviceBrand: undefined;
show(): never {
throw new Error('not implemented');
}
confirm(): never {
throw new Error('not implemented');
}
about(): never {
throw new Error('not implemented');
}
input(): never {
throw new Error('not implemented');
}
};
const emptyCommandService: ICommandService = {
_serviceBrand: undefined,
onWillExecuteCommand: () => Disposable.None,
onDidExecuteCommand: () => Disposable.None,
executeCommand: (commandId: string, ...args: any[]): Promise<any> => {
return Promise.resolve(undefined);
}
};
const emptyNotificationService = new class implements INotificationService {
declare readonly _serviceBrand: undefined;
notify(...args: any[]): never {
throw new Error('not implemented');
}
info(...args: any[]): never {
throw new Error('not implemented');
}
warn(...args: any[]): never {
throw new Error('not implemented');
}
error(...args: any[]): never {
throw new Error('not implemented');
}
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle {
throw new Error('not implemented');
}
status(message: string | Error, options?: IStatusMessageOptions): IDisposable {
return Disposable.None;
}
setFilter(filter: NotificationsFilter): void {
throw new Error('not implemented.');
}
};
class EmptyNotificationService implements INotificationService {
declare readonly _serviceBrand: undefined;
constructor(private withNotify: (notification: INotification) => void) {
}
notify(notification: INotification): INotificationHandle {
this.withNotify(notification);
return new NoOpNotification();
}
info(message: any): void {
throw new Error('Method not implemented.');
}
warn(message: any): void {
throw new Error('Method not implemented.');
}
error(message: any): void {
throw new Error('Method not implemented.');
}
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle {
throw new Error('Method not implemented');
}
status(message: string, options?: IStatusMessageOptions): IDisposable {
return Disposable.None;
}
setFilter(filter: NotificationsFilter): void {
throw new Error('Method not implemented.');
}
}
suite('ExtHostMessageService', function () {
test('propagte handle on select', async function () {
let service = new MainThreadMessageService(null!, new EmptyNotificationService(notification => {
assert.equal(notification.actions!.primary!.length, 1);
platform.setImmediate(() => notification.actions!.primary![0].run());
}), emptyCommandService, emptyDialogService);
const handle = await service.$showMessage(1, 'h', {}, [{ handle: 42, title: 'a thing', isCloseAffordance: true }]);
assert.equal(handle, 42);
});
suite('modal', () => {
test('calls dialog service', async () => {
const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock<IDialogService>() {
show(severity: Severity, message: string, buttons: string[]) {
assert.equal(severity, 1);
assert.equal(message, 'h');
assert.equal(buttons.length, 2);
assert.equal(buttons[1], 'Cancel');
return Promise.resolve({ choice: 0 });
}
} as IDialogService);
const handle = await service.$showMessage(1, 'h', { modal: true }, [{ handle: 42, title: 'a thing', isCloseAffordance: false }]);
assert.equal(handle, 42);
});
test('returns undefined when cancelled', async () => {
const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock<IDialogService>() {
show() {
return Promise.resolve({ choice: 1 });
}
} as IDialogService);
const handle = await service.$showMessage(1, 'h', { modal: true }, [{ handle: 42, title: 'a thing', isCloseAffordance: false }]);
assert.equal(handle, undefined);
});
test('hides Cancel button when not needed', async () => {
const service = new MainThreadMessageService(null!, emptyNotificationService, emptyCommandService, new class extends mock<IDialogService>() {
show(severity: Severity, message: string, buttons: string[]) {
assert.equal(buttons.length, 1);
return Promise.resolve({ choice: 0 });
}
} as IDialogService);
const handle = await service.$showMessage(1, 'h', { modal: true }, [{ handle: 42, title: 'a thing', isCloseAffordance: true }]);
assert.equal(handle, 42);
});
});
});

View File

@@ -0,0 +1,308 @@
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { NullLogService } from 'vs/platform/log/common/log';
import { mock } from 'vs/base/test/common/mock';
import { IModelAddedData, MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { URI } from 'vs/base/common/uri';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { isEqual } from 'vs/base/common/resources';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { generateUuid } from 'vs/base/common/uuid';
suite('NotebookCell#Document', function () {
let rpcProtocol: TestRPCProtocol;
let notebook: ExtHostNotebookDocument;
let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
let extHostDocuments: ExtHostDocuments;
let extHostNotebooks: ExtHostNotebookController;
const notebookUri = URI.parse('test:///notebook.file');
const disposables = new DisposableStore();
setup(async function () {
disposables.clear();
rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
$registerCommand() { }
});
rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {
async $registerNotebookProvider() { }
async $unregisterNotebookProvider() { }
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
const extHostStoragePaths = new class extends mock<IExtensionStoragePaths>() {
workspaceValue() {
return URI.from({ scheme: 'test', path: generateUuid() });
}
};
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths);
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { }
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({
addedDocuments: [{
uri: notebookUri,
viewType: 'test',
versionId: 0,
cells: [{
handle: 0,
uri: CellUri.generate(notebookUri, 0),
source: ['### Heading'],
eol: '\n',
language: 'markdown',
cellKind: CellKind.Markdown,
outputs: [],
}, {
handle: 1,
uri: CellUri.generate(notebookUri, 1),
source: ['console.log("aaa")', 'console.log("bbb")'],
eol: '\n',
language: 'javascript',
cellKind: CellKind.Code,
outputs: [],
}],
contentOptions: { transientMetadata: {}, transientOutputs: false }
}],
addedEditors: [{
documentUri: notebookUri,
id: '_notebook_editor_0',
selections: [0],
visibleRanges: []
}]
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
notebook = extHostNotebooks.notebookDocuments[0]!;
disposables.add(reg);
disposables.add(notebook);
disposables.add(extHostDocuments);
});
test('cell document is vscode.TextDocument', async function () {
assert.strictEqual(notebook.notebookDocument.cells.length, 2);
const [c1, c2] = notebook.notebookDocument.cells;
const d1 = extHostDocuments.getDocument(c1.uri);
assert.ok(d1);
assert.equal(d1.languageId, c1.language);
assert.equal(d1.version, 1);
assert.ok(d1.notebook === notebook.notebookDocument);
const d2 = extHostDocuments.getDocument(c2.uri);
assert.ok(d2);
assert.equal(d2.languageId, c2.language);
assert.equal(d2.version, 1);
assert.ok(d2.notebook === notebook.notebookDocument);
});
test('cell document goes when notebook closes', async function () {
const cellUris: string[] = [];
for (let cell of notebook.notebookDocument.cells) {
assert.ok(extHostDocuments.getDocument(cell.uri));
cellUris.push(cell.uri.toString());
}
const removedCellUris: string[] = [];
const reg = extHostDocuments.onDidRemoveDocument(doc => {
removedCellUris.push(doc.uri.toString());
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
reg.dispose();
assert.strictEqual(removedCellUris.length, 2);
assert.deepStrictEqual(removedCellUris.sort(), cellUris.sort());
});
test('cell document is vscode.TextDocument after changing it', async function () {
const p = new Promise<void>((resolve, reject) => {
extHostNotebooks.onDidChangeNotebookCells(e => {
try {
assert.strictEqual(e.changes.length, 1);
assert.strictEqual(e.changes[0].items.length, 2);
const [first, second] = e.changes[0].items;
const doc1 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, first.uri));
assert.ok(doc1);
assert.strictEqual(doc1?.document === first.document, true);
const doc2 = extHostDocuments.getAllDocumentData().find(data => isEqual(data.document.uri, second.uri));
assert.ok(doc2);
assert.strictEqual(doc2?.document === second.document, true);
resolve();
} catch (err) {
reject(err);
}
});
});
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 2,
uri: CellUri.generate(notebookUri, 2),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 3,
uri: CellUri.generate(notebookUri, 3),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
await p;
});
test('cell document stays open when notebook is still open', async function () {
const docs: vscode.TextDocument[] = [];
const addData: IModelAddedData[] = [];
for (let cell of notebook.notebookDocument.cells) {
const doc = extHostDocuments.getDocument(cell.uri);
assert.ok(doc);
assert.equal(extHostDocuments.getDocument(cell.uri).isClosed, false);
docs.push(doc);
addData.push({
EOL: '\n',
isDirty: doc.isDirty,
lines: doc.getText().split('\n'),
modeId: doc.languageId,
uri: doc.uri,
versionId: doc.version
});
}
// this call happens when opening a document on the main side
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addData });
// this call happens when closing a document from the main side
extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: docs.map(d => d.uri) });
// notebook is still open -> cell documents stay open
for (let cell of notebook.notebookDocument.cells) {
assert.ok(extHostDocuments.getDocument(cell.uri));
assert.equal(extHostDocuments.getDocument(cell.uri).isClosed, false);
}
// close notebook -> docs are closed
extHostNotebooks.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
for (let cell of notebook.notebookDocument.cells) {
assert.throws(() => extHostDocuments.getDocument(cell.uri));
}
for (let doc of docs) {
assert.equal(doc.isClosed, true);
}
});
test('cell document goes when cell is removed', async function () {
assert.equal(notebook.notebookDocument.cells.length, 2);
const [cell1, cell2] = notebook.notebookDocument.cells;
extHostNotebooks.$acceptModelChanged(notebook.uri, {
versionId: 2,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 1, []]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1);
assert.equal(cell1.document.isClosed, true); // ref still alive!
assert.equal(cell2.document.isClosed, false);
assert.throws(() => extHostDocuments.getDocument(cell1.uri));
});
test('cell document knows notebook', function () {
for (let cells of notebook.notebookDocument.cells) {
assert.equal(cells.document.notebook === notebook.notebookDocument, true);
}
});
test('cell#index', function () {
assert.equal(notebook.notebookDocument.cells.length, 2);
const [first, second] = notebook.notebookDocument.cells;
assert.equal(first.index, 0);
assert.equal(second.index, 1);
// remove first cell
extHostNotebooks.$acceptModelChanged(notebook.uri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 1, []]]
}]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1);
assert.equal(second.index, 0);
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 2,
uri: CellUri.generate(notebookUri, 2),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 3,
uri: CellUri.generate(notebookUri, 3),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 3);
assert.equal(second.index, 2);
});
});

View File

@@ -0,0 +1,606 @@
/*---------------------------------------------------------------------------------------------
* 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 { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { NullLogService } from 'vs/platform/log/common/log';
import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { URI } from 'vs/base/common/uri';
import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Position, Location, Range } from 'vs/workbench/api/common/extHostTypes';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import * as vscode from 'vscode';
import { mock } from 'vs/workbench/test/common/workbenchTestServices';
import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { generateUuid } from 'vs/base/common/uuid';
suite('NotebookConcatDocument', function () {
let rpcProtocol: TestRPCProtocol;
let notebook: ExtHostNotebookDocument;
let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
let extHostDocuments: ExtHostDocuments;
let extHostNotebooks: ExtHostNotebookController;
const notebookUri = URI.parse('test:///notebook.file');
const disposables = new DisposableStore();
setup(async function () {
disposables.clear();
rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
$registerCommand() { }
});
rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {
async $registerNotebookProvider() { }
async $unregisterNotebookProvider() { }
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
const extHostStoragePaths = new class extends mock<IExtensionStoragePaths>() {
workspaceValue() {
return URI.from({ scheme: 'test', path: generateUuid() });
}
};
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths);
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { }
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({
addedDocuments: [{
uri: notebookUri,
viewType: 'test',
cells: [{
handle: 0,
uri: CellUri.generate(notebookUri, 0),
source: ['### Heading'],
eol: '\n',
language: 'markdown',
cellKind: CellKind.Markdown,
outputs: [],
}],
contentOptions: { transientOutputs: false, transientMetadata: {} },
versionId: 0
}],
addedEditors: [
{
documentUri: notebookUri,
id: '_notebook_editor_0',
selections: [0],
visibleRanges: []
}
]
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
notebook = extHostNotebooks.notebookDocuments[0]!;
disposables.add(reg);
disposables.add(notebook);
disposables.add(extHostDocuments);
});
test('empty', function () {
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assert.equal(doc.getText(), '');
assert.equal(doc.version, 0);
// assert.equal(doc.locationAt(new Position(0, 0)), undefined);
// assert.equal(doc.positionAt(SOME_FAKE_LOCATION?), undefined);
});
function assertLocation(doc: vscode.NotebookConcatTextDocument, pos: Position, expected: Location, reverse = true) {
const actual = doc.locationAt(pos);
assert.equal(actual.uri.toString(), expected.uri.toString());
assert.equal(actual.range.isEqual(expected.range), true);
if (reverse) {
// reverse - offset
const offset = doc.offsetAt(pos);
assert.equal(doc.positionAt(offset).isEqual(pos), true);
// reverse - pos
const actualPosition = doc.positionAt(actual);
assert.equal(actualPosition.isEqual(pos), true);
}
}
function assertLines(doc: vscode.NotebookConcatTextDocument, ...lines: string[]) {
let actual = doc.getText().split(/\r\n|\n|\r/);
assert.deepStrictEqual(actual, lines);
}
test('contains', function () {
const cellUri1 = CellUri.generate(notebook.uri, 1);
const cellUri2 = CellUri.generate(notebook.uri, 2);
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: cellUri1,
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: cellUri2,
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]
]
}]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assert.equal(doc.contains(cellUri1), true);
assert.equal(doc.contains(cellUri2), true);
assert.equal(doc.contains(URI.parse('some://miss/path')), false);
});
test('location, position mapping', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(0, 0)));
assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 0)));
assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 3)));
assertLocation(doc, new Position(5, 11), new Location(notebook.notebookDocument.cells[1].uri, new Position(2, 11)));
assertLocation(doc, new Position(5, 12), new Location(notebook.notebookDocument.cells[1].uri, new Position(2, 11)), false); // don't check identity because position will be clamped
});
test('location, position mapping, cell changes', function () {
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
// UPDATE 1
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 1);
assert.equal(doc.version, 1);
assertLines(doc, 'Hello', 'World', 'Hello World!');
assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(0, 0)));
assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 2)));
assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 12)), false); // clamped
// UPDATE 2
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[1, 0, [{
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2);
assert.equal(doc.version, 2);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(0, 0)));
assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 0)));
assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 3)));
assertLocation(doc, new Position(5, 11), new Location(notebook.notebookDocument.cells[1].uri, new Position(2, 11)));
assertLocation(doc, new Position(5, 12), new Location(notebook.notebookDocument.cells[1].uri, new Position(2, 11)), false); // don't check identity because position will be clamped
// UPDATE 3 (remove cell #2 again)
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[1, 1, []]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 1);
assert.equal(doc.version, 3);
assertLines(doc, 'Hello', 'World', 'Hello World!');
assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(0, 0)));
assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 2)));
assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 12)), false); // clamped
});
test('location, position mapping, cell-document changes', function () {
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
// UPDATE 1
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2);
assert.equal(doc.version, 1);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertLocation(doc, new Position(0, 0), new Location(notebook.notebookDocument.cells[0].uri, new Position(0, 0)));
assertLocation(doc, new Position(2, 2), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 2)));
assertLocation(doc, new Position(2, 12), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 12)));
assertLocation(doc, new Position(4, 0), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 0)));
assertLocation(doc, new Position(4, 3), new Location(notebook.notebookDocument.cells[1].uri, new Position(1, 3)));
// offset math
let cell1End = doc.offsetAt(new Position(2, 12));
assert.equal(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true);
extHostDocuments.$acceptModelChanged(notebook.notebookDocument.cells[0].uri, {
versionId: 0,
eol: '\n',
changes: [{
range: { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 6 },
rangeLength: 6,
rangeOffset: 12,
text: 'Hi'
}]
}, false);
assertLines(doc, 'Hello', 'World', 'Hi World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertLocation(doc, new Position(2, 12), new Location(notebook.notebookDocument.cells[0].uri, new Position(2, 9)), false);
assert.equal(doc.positionAt(cell1End).isEqual(new Position(3, 2)), true);
});
test('selector', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['fooLang-document'],
eol: '\n',
language: 'fooLang',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['barLang-document'],
eol: '\n',
language: 'barLang',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
const mixedDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
const fooLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, 'fooLang');
const barLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, 'barLang');
assertLines(mixedDoc, 'fooLang-document', 'barLang-document');
assertLines(fooLangDoc, 'fooLang-document');
assertLines(barLangDoc, 'barLang-document');
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[2, 0, [{
handle: 3,
uri: CellUri.generate(notebook.uri, 3),
source: ['barLang-document2'],
eol: '\n',
language: 'barLang',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assertLines(mixedDoc, 'fooLang-document', 'barLang-document', 'barLang-document2');
assertLines(fooLangDoc, 'fooLang-document');
assertLines(barLangDoc, 'barLang-document', 'barLang-document2');
});
function assertOffsetAtPosition(doc: vscode.NotebookConcatTextDocument, offset: number, expected: { line: number, character: number }, reverse = true) {
const actual = doc.positionAt(offset);
assert.equal(actual.line, expected.line);
assert.equal(actual.character, expected.character);
if (reverse) {
const actualOffset = doc.offsetAt(actual);
assert.equal(actualOffset, offset);
}
}
test('offsetAt(position) <-> positionAt(offset)', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertOffsetAtPosition(doc, 0, { line: 0, character: 0 });
assertOffsetAtPosition(doc, 1, { line: 0, character: 1 });
assertOffsetAtPosition(doc, 9, { line: 1, character: 3 });
assertOffsetAtPosition(doc, 32, { line: 4, character: 1 });
assertOffsetAtPosition(doc, 47, { line: 5, character: 11 });
});
function assertLocationAtPosition(doc: vscode.NotebookConcatTextDocument, pos: { line: number, character: number }, expected: { uri: URI, line: number, character: number }, reverse = true) {
const actual = doc.locationAt(new Position(pos.line, pos.character));
assert.equal(actual.uri.toString(), expected.uri.toString());
assert.equal(actual.range.start.line, expected.line);
assert.equal(actual.range.end.line, expected.line);
assert.equal(actual.range.start.character, expected.character);
assert.equal(actual.range.end.character, expected.character);
if (reverse) {
const actualPos = doc.positionAt(actual);
assert.equal(actualPos.line, pos.line);
assert.equal(actualPos.character, pos.character);
}
}
test('locationAt(position) <-> positionAt(location)', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assertLocationAtPosition(doc, { line: 0, character: 0 }, { uri: notebook.notebookDocument.cells[0].uri, line: 0, character: 0 });
assertLocationAtPosition(doc, { line: 2, character: 0 }, { uri: notebook.notebookDocument.cells[0].uri, line: 2, character: 0 });
assertLocationAtPosition(doc, { line: 2, character: 12 }, { uri: notebook.notebookDocument.cells[0].uri, line: 2, character: 12 });
assertLocationAtPosition(doc, { line: 3, character: 0 }, { uri: notebook.notebookDocument.cells[1].uri, line: 0, character: 0 });
assertLocationAtPosition(doc, { line: 5, character: 0 }, { uri: notebook.notebookDocument.cells[1].uri, line: 2, character: 0 });
assertLocationAtPosition(doc, { line: 5, character: 11 }, { uri: notebook.notebookDocument.cells[1].uri, line: 2, character: 11 });
});
test('getText(range)', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
assert.equal(doc.getText(new Range(0, 0, 0, 0)), '');
assert.equal(doc.getText(new Range(0, 0, 1, 0)), 'Hello\n');
assert.equal(doc.getText(new Range(2, 0, 4, 0)), 'Hello World!\nHallo\n');
});
test('validateRange/Position', function () {
extHostNotebooks.$acceptModelChanged(notebookUri, {
versionId: notebook.notebookDocument.version + 1,
rawEvents: [
{
kind: NotebookCellsChangeType.ModelChange,
changes: [[0, 0, [{
handle: 1,
uri: CellUri.generate(notebook.uri, 1),
source: ['Hello', 'World', 'Hello World!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}, {
handle: 2,
uri: CellUri.generate(notebook.uri, 2),
source: ['Hallo', 'Welt', 'Hallo Welt!'],
eol: '\n',
language: 'test',
cellKind: CellKind.Code,
outputs: [],
}]]]
}
]
}, false);
assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code
let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined);
assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!');
function assertPosition(actual: vscode.Position, expectedLine: number, expectedCh: number) {
assert.equal(actual.line, expectedLine);
assert.equal(actual.character, expectedCh);
}
// "fixed"
assertPosition(doc.validatePosition(new Position(0, 1000)), 0, 5);
assertPosition(doc.validatePosition(new Position(2, 1000)), 2, 12);
assertPosition(doc.validatePosition(new Position(5, 1000)), 5, 11);
assertPosition(doc.validatePosition(new Position(5000, 1000)), 5, 11);
// "good"
assertPosition(doc.validatePosition(new Position(0, 1)), 0, 1);
assertPosition(doc.validatePosition(new Position(0, 5)), 0, 5);
assertPosition(doc.validatePosition(new Position(2, 8)), 2, 8);
assertPosition(doc.validatePosition(new Position(2, 12)), 2, 12);
assertPosition(doc.validatePosition(new Position(5, 11)), 5, 11);
});
});

View File

@@ -0,0 +1,513 @@
/*---------------------------------------------------------------------------------------------
* 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 { TextEditorLineNumbersStyle, Range } from 'vs/workbench/api/common/extHostTypes';
import { TextEditorCursorStyle, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
import { MainThreadTextEditorsShape, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostTextEditorOptions, ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { NullLogService } from 'vs/platform/log/common/log';
suite('ExtHostTextEditor', () => {
let editor: ExtHostTextEditor;
let doc = new ExtHostDocumentData(undefined!, URI.file(''), [
'aaaa bbbb+cccc abc'
], '\n', 1, 'text', false);
setup(() => {
editor = new ExtHostTextEditor('fake', null!, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
});
test('disposed editor', () => {
assert.ok(editor.document);
editor._acceptViewColumn(3);
assert.equal(3, editor.viewColumn);
editor.dispose();
assert.throws(() => editor._acceptViewColumn(2));
assert.equal(3, editor.viewColumn);
assert.ok(editor.document);
assert.throws(() => editor._acceptOptions(null!));
assert.throws(() => editor._acceptSelections([]));
});
test('API [bug]: registerTextEditorCommand clears redo stack even if no edits are made #55163', async function () {
let applyCount = 0;
let editor = new ExtHostTextEditor('edt1',
new class extends mock<MainThreadTextEditorsShape>() {
$tryApplyEdits(): Promise<boolean> {
applyCount += 1;
return Promise.resolve(true);
}
}, new NullLogService(), doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1);
await editor.edit(edit => { });
assert.equal(applyCount, 0);
await editor.edit(edit => { edit.setEndOfLine(1); });
assert.equal(applyCount, 1);
await editor.edit(edit => { edit.delete(new Range(0, 0, 1, 1)); });
assert.equal(applyCount, 2);
});
});
suite('ExtHostTextEditorOptions', () => {
let opts: ExtHostTextEditorOptions;
let calls: ITextEditorConfigurationUpdate[] = [];
setup(() => {
calls = [];
let mockProxy: MainThreadTextEditorsShape = {
dispose: undefined!,
$trySetOptions: (id: string, options: ITextEditorConfigurationUpdate) => {
assert.equal(id, '1');
calls.push(options);
return Promise.resolve(undefined);
},
$tryShowTextDocument: undefined!,
$registerTextEditorDecorationType: undefined!,
$removeTextEditorDecorationType: undefined!,
$tryShowEditor: undefined!,
$tryHideEditor: undefined!,
$trySetDecorations: undefined!,
$trySetDecorationsFast: undefined!,
$tryRevealRange: undefined!,
$trySetSelections: undefined!,
$tryApplyEdits: undefined!,
$tryInsertSnippet: undefined!,
$getDiffInformation: undefined!
};
opts = new ExtHostTextEditorOptions(mockProxy, '1', {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
}, new NullLogService());
});
teardown(() => {
opts = null!;
calls = null!;
});
function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void {
let actual = {
tabSize: opts.tabSize,
indentSize: opts.indentSize,
insertSpaces: opts.insertSpaces,
cursorStyle: opts.cursorStyle,
lineNumbers: opts.lineNumbers
};
assert.deepEqual(actual, expected);
}
test('can set tabSize to the same value', () => {
opts.tabSize = 4;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can change tabSize to positive integer', () => {
opts.tabSize = 1;
assertState(opts, {
tabSize: 1,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 1 }]);
});
test('can change tabSize to positive float', () => {
opts.tabSize = 2.3;
assertState(opts, {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 2 }]);
});
test('can change tabSize to a string number', () => {
opts.tabSize = '2';
assertState(opts, {
tabSize: 2,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 2 }]);
});
test('tabSize can request indentation detection', () => {
opts.tabSize = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 'auto' }]);
});
test('ignores invalid tabSize 1', () => {
opts.tabSize = null!;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid tabSize 2', () => {
opts.tabSize = -5;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid tabSize 3', () => {
opts.tabSize = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid tabSize 4', () => {
opts.tabSize = '-17';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can set indentSize to the same value', () => {
opts.indentSize = 4;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can change indentSize to positive integer', () => {
opts.indentSize = 1;
assertState(opts, {
tabSize: 4,
indentSize: 1,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ indentSize: 1 }]);
});
test('can change indentSize to positive float', () => {
opts.indentSize = 2.3;
assertState(opts, {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ indentSize: 2 }]);
});
test('can change indentSize to a string number', () => {
opts.indentSize = '2';
assertState(opts, {
tabSize: 4,
indentSize: 2,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ indentSize: 2 }]);
});
test('indentSize can request to use tabSize', () => {
opts.indentSize = 'tabSize';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ indentSize: 'tabSize' }]);
});
test('indentSize cannot request indentation detection', () => {
opts.indentSize = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid indentSize 1', () => {
opts.indentSize = null!;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid indentSize 2', () => {
opts.indentSize = -5;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid indentSize 3', () => {
opts.indentSize = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('ignores invalid indentSize 4', () => {
opts.indentSize = '-17';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can set insertSpaces to the same value', () => {
opts.insertSpaces = false;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can set insertSpaces to boolean', () => {
opts.insertSpaces = true;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ insertSpaces: true }]);
});
test('can set insertSpaces to false string', () => {
opts.insertSpaces = 'false';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can set insertSpaces to truey', () => {
opts.insertSpaces = 'hello';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ insertSpaces: true }]);
});
test('insertSpaces can request indentation detection', () => {
opts.insertSpaces = 'auto';
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ insertSpaces: 'auto' }]);
});
test('can set cursorStyle to same value', () => {
opts.cursorStyle = TextEditorCursorStyle.Line;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can change cursorStyle', () => {
opts.cursorStyle = TextEditorCursorStyle.Block;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ cursorStyle: TextEditorCursorStyle.Block }]);
});
test('can set lineNumbers to same value', () => {
opts.lineNumbers = TextEditorLineNumbersStyle.On;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can change lineNumbers', () => {
opts.lineNumbers = TextEditorLineNumbersStyle.Off;
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.Off
});
assert.deepEqual(calls, [{ lineNumbers: RenderLineNumbersType.Off }]);
});
test('can do bulk updates 0', () => {
opts.assign({
tabSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: TextEditorLineNumbersStyle.On
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, []);
});
test('can do bulk updates 1', () => {
opts.assign({
tabSize: 'auto',
insertSpaces: true
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: true,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 'auto', insertSpaces: true }]);
});
test('can do bulk updates 2', () => {
opts.assign({
tabSize: 3,
insertSpaces: 'auto'
});
assertState(opts, {
tabSize: 3,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Line,
lineNumbers: RenderLineNumbersType.On
});
assert.deepEqual(calls, [{ tabSize: 3, insertSpaces: 'auto' }]);
});
test('can do bulk updates 3', () => {
opts.assign({
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: TextEditorLineNumbersStyle.Relative
});
assertState(opts, {
tabSize: 4,
indentSize: 4,
insertSpaces: false,
cursorStyle: TextEditorCursorStyle.Block,
lineNumbers: RenderLineNumbersType.Relative
});
assert.deepEqual(calls, [{ cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.Relative }]);
});
});

View File

@@ -0,0 +1,742 @@
/*---------------------------------------------------------------------------------------------
* 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 sinon from 'sinon';
import { Emitter } from 'vs/base/common/event';
import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { MainThreadTreeViewsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { TreeDataProvider, TreeItem } from 'vscode';
import { TestRPCProtocol } from './testRPCProtocol';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { mock } from 'vs/base/test/common/mock';
import { TreeItemCollapsibleState, ITreeItem } from 'vs/workbench/common/views';
import { NullLogService } from 'vs/platform/log/common/log';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import type { IDisposable } from 'vs/base/common/lifecycle';
suite('ExtHostTreeView', function () {
class RecordingShape extends mock<MainThreadTreeViewsShape>() {
onRefresh = new Emitter<{ [treeItemHandle: string]: ITreeItem }>();
$registerTreeViewDataProvider(treeViewId: string): void {
}
$refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise<void> {
return Promise.resolve(null).then(() => {
this.onRefresh.fire(itemsToRefresh);
});
}
$reveal(): Promise<void> {
return Promise.resolve();
}
}
let testObject: ExtHostTreeViews;
let target: RecordingShape;
let onDidChangeTreeNode: Emitter<{ key: string } | undefined>;
let onDidChangeTreeNodeWithId: Emitter<{ key: string }>;
let tree: { [key: string]: any };
let labels: { [key: string]: string };
let nodes: { [key: string]: { key: string } };
setup(() => {
tree = {
'a': {
'aa': {},
'ab': {}
},
'b': {
'ba': {},
'bb': {}
}
};
labels = {};
nodes = {};
let rpcProtocol = new TestRPCProtocol();
// Use IInstantiationService to get typechecking when instantiating
let inst: IInstantiationService;
{
let instantiationService = new TestInstantiationService();
inst = instantiationService;
}
rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol));
target = new RecordingShape();
testObject = new ExtHostTreeViews(target, new ExtHostCommands(
rpcProtocol,
new NullLogService()
), new NullLogService());
onDidChangeTreeNode = new Emitter<{ key: string } | undefined>();
onDidChangeTreeNodeWithId = new Emitter<{ key: string }>();
testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
testObject.createTreeView('testNodeWithHighlightsTreeProvider', { treeDataProvider: aNodeWithHighlightedLabelTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return loadCompleteTree('testNodeTreeProvider');
});
test('construct node tree', () => {
return testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:a', '0/0:b']);
return Promise.all([
testObject.$getChildren('testNodeTreeProvider', '0/0:a')
.then(children => {
const actuals = children.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:a/0:aa', '0/0:a/0:ab']);
return Promise.all([
testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:aa').then(children => assert.equal(children.length, 0)),
testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:ab').then(children => assert.equal(children.length, 0))
]);
}),
testObject.$getChildren('testNodeTreeProvider', '0/0:b')
.then(children => {
const actuals = children.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:b/0:ba', '0/0:b/0:bb']);
return Promise.all([
testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:ba').then(children => assert.equal(children.length, 0)),
testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:bb').then(children => assert.equal(children.length, 0))
]);
})
]);
});
});
test('construct id tree', () => {
return testObject.$getChildren('testNodeWithIdTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['1/a', '1/b']);
return Promise.all([
testObject.$getChildren('testNodeWithIdTreeProvider', '1/a')
.then(children => {
const actuals = children.map(e => e.handle);
assert.deepEqual(actuals, ['1/aa', '1/ab']);
return Promise.all([
testObject.$getChildren('testNodeWithIdTreeProvider', '1/aa').then(children => assert.equal(children.length, 0)),
testObject.$getChildren('testNodeWithIdTreeProvider', '1/ab').then(children => assert.equal(children.length, 0))
]);
}),
testObject.$getChildren('testNodeWithIdTreeProvider', '1/b')
.then(children => {
const actuals = children.map(e => e.handle);
assert.deepEqual(actuals, ['1/ba', '1/bb']);
return Promise.all([
testObject.$getChildren('testNodeWithIdTreeProvider', '1/ba').then(children => assert.equal(children.length, 0)),
testObject.$getChildren('testNodeWithIdTreeProvider', '1/bb').then(children => assert.equal(children.length, 0))
]);
})
]);
});
});
test('construct highlights tree', () => {
return testObject.$getChildren('testNodeWithHighlightsTreeProvider')
.then(elements => {
assert.deepEqual(removeUnsetKeys(elements), [{
handle: '1/a',
label: { label: 'a', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.Collapsed
}, {
handle: '1/b',
label: { label: 'b', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.Collapsed
}]);
return Promise.all([
testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/a')
.then(children => {
assert.deepEqual(removeUnsetKeys(children), [{
handle: '1/aa',
parentHandle: '1/a',
label: { label: 'aa', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.None
}, {
handle: '1/ab',
parentHandle: '1/a',
label: { label: 'ab', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.None
}]);
}),
testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/b')
.then(children => {
assert.deepEqual(removeUnsetKeys(children), [{
handle: '1/ba',
parentHandle: '1/b',
label: { label: 'ba', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.None
}, {
handle: '1/bb',
parentHandle: '1/b',
label: { label: 'bb', highlights: [[0, 2], [3, 5]] },
collapsibleState: TreeItemCollapsibleState.None
}]);
})
]);
});
});
test('error is thrown if id is not unique', (done) => {
tree['a'] = {
'aa': {},
};
tree['b'] = {
'aa': {},
'ba': {}
};
target.onRefresh.event(() => {
testObject.$getChildren('testNodeWithIdTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['1/a', '1/b']);
return testObject.$getChildren('testNodeWithIdTreeProvider', '1/a')
.then(() => testObject.$getChildren('testNodeWithIdTreeProvider', '1/b'))
.then(() => assert.fail('Should fail with duplicate id'))
.finally(done);
});
});
onDidChangeTreeNode.fire(undefined);
});
test('refresh root', function (done) {
target.onRefresh.event(actuals => {
assert.equal(undefined, actuals);
done();
});
onDidChangeTreeNode.fire(undefined);
});
test('refresh a parent node', () => {
return new Promise((c, e) => {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:b'], Object.keys(actuals));
assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), {
handle: '0/0:b',
label: { label: 'b' },
collapsibleState: TreeItemCollapsibleState.Collapsed
});
c(undefined);
});
onDidChangeTreeNode.fire(getNode('b'));
});
});
test('refresh a leaf node', function (done) {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:b/0:bb'], Object.keys(actuals));
assert.deepEqual(removeUnsetKeys(actuals['0/0:b/0:bb']), {
handle: '0/0:b/0:bb',
parentHandle: '0/0:b',
label: { label: 'bb' },
collapsibleState: TreeItemCollapsibleState.None
});
done();
});
onDidChangeTreeNode.fire(getNode('bb'));
});
async function runWithEventMerging(action: (resolve: () => void) => void) {
await new Promise<void>((resolve) => {
let subscription: IDisposable | undefined = undefined;
subscription = target.onRefresh.event(() => {
subscription!.dispose();
resolve();
});
onDidChangeTreeNode.fire(getNode('b'));
});
await new Promise<void>(action);
}
test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals));
assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), {
handle: '0/0:b',
label: { label: 'b' },
collapsibleState: TreeItemCollapsibleState.Collapsed
});
assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {
handle: '0/0:a/0:aa',
parentHandle: '0/0:a',
label: { label: 'aa' },
collapsibleState: TreeItemCollapsibleState.None
});
resolve();
});
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(getNode('aa'));
onDidChangeTreeNode.fire(getNode('bb'));
});
});
test('refresh parent and child node trigger refresh only on parent - scenario 2', async () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals));
assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), {
handle: '0/0:b',
label: { label: 'b' },
collapsibleState: TreeItemCollapsibleState.Collapsed
});
assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {
handle: '0/0:a/0:aa',
parentHandle: '0/0:a',
label: { label: 'aa' },
collapsibleState: TreeItemCollapsibleState.None
});
resolve();
});
onDidChangeTreeNode.fire(getNode('bb'));
onDidChangeTreeNode.fire(getNode('aa'));
onDidChangeTreeNode.fire(getNode('b'));
});
});
test('refresh an element for label change', function (done) {
labels['a'] = 'aa';
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:a'], Object.keys(actuals));
assert.deepEqual(removeUnsetKeys(actuals['0/0:a']), {
handle: '0/0:aa',
label: { label: 'aa' },
collapsibleState: TreeItemCollapsibleState.Collapsed
});
done();
});
onDidChangeTreeNode.fire(getNode('a'));
});
test('refresh calls are throttled on roots', () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.equal(undefined, actuals);
resolve();
});
onDidChangeTreeNode.fire(undefined);
onDidChangeTreeNode.fire(undefined);
onDidChangeTreeNode.fire(undefined);
onDidChangeTreeNode.fire(undefined);
});
});
test('refresh calls are throttled on elements', () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals));
resolve();
});
onDidChangeTreeNode.fire(getNode('a'));
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(getNode('a'));
});
});
test('refresh calls are throttled on unknown elements', () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals));
resolve();
});
onDidChangeTreeNode.fire(getNode('a'));
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(getNode('g'));
onDidChangeTreeNode.fire(getNode('a'));
});
});
test('refresh calls are throttled on unknown elements and root', () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.equal(undefined, actuals);
resolve();
});
onDidChangeTreeNode.fire(getNode('a'));
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(getNode('g'));
onDidChangeTreeNode.fire(undefined);
});
});
test('refresh calls are throttled on elements and root', () => {
return runWithEventMerging((resolve) => {
target.onRefresh.event(actuals => {
assert.equal(undefined, actuals);
resolve();
});
onDidChangeTreeNode.fire(getNode('a'));
onDidChangeTreeNode.fire(getNode('b'));
onDidChangeTreeNode.fire(undefined);
onDidChangeTreeNode.fire(getNode('a'));
});
});
test('generate unique handles from labels by escaping them', (done) => {
tree = {
'a/0:b': {}
};
target.onRefresh.event(() => {
testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
assert.deepEqual(elements.map(e => e.handle), ['0/0:a//0:b']);
done();
});
});
onDidChangeTreeNode.fire(undefined);
});
test('tree with duplicate labels', (done) => {
const dupItems = {
'adup1': 'c',
'adup2': 'g',
'bdup1': 'e',
'hdup1': 'i',
'hdup2': 'l',
'jdup1': 'k'
};
labels['c'] = 'a';
labels['e'] = 'b';
labels['g'] = 'a';
labels['i'] = 'h';
labels['l'] = 'h';
labels['k'] = 'j';
tree[dupItems['adup1']] = {};
tree['d'] = {};
const bdup1Tree: { [key: string]: any } = {};
bdup1Tree['h'] = {};
bdup1Tree[dupItems['hdup1']] = {};
bdup1Tree['j'] = {};
bdup1Tree[dupItems['jdup1']] = {};
bdup1Tree[dupItems['hdup2']] = {};
tree[dupItems['bdup1']] = bdup1Tree;
tree['f'] = {};
tree[dupItems['adup2']] = {};
target.onRefresh.event(() => {
testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']);
return testObject.$getChildren('testNodeTreeProvider', '0/1:b')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['0/1:b/0:h', '0/1:b/1:h', '0/1:b/0:j', '0/1:b/1:j', '0/1:b/2:h']);
done();
});
});
});
onDidChangeTreeNode.fire(undefined);
});
test('getChildren is not returned from cache if refreshed', (done) => {
tree = {
'c': {}
};
target.onRefresh.event(() => {
testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
assert.deepEqual(elements.map(e => e.handle), ['0/0:c']);
done();
});
});
onDidChangeTreeNode.fire(undefined);
});
test('getChildren is returned from cache if not refreshed', () => {
tree = {
'c': {}
};
return testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
assert.deepEqual(elements.map(e => e.handle), ['0/0:a', '0/0:b']);
});
});
test('reveal will throw an error if getParent is not implemented', () => {
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return treeView.reveal({ key: 'a' })
.then(() => assert.fail('Reveal should throw an error as getParent is not implemented'), () => null);
});
test('reveal will return empty array for root element', () => {
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return treeView.reveal({ key: 'a' })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([], revealTarget.args[0][2]);
assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]);
});
});
test('reveal will return parents array for an element when hierarchy is not loaded', () => {
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return treeView.reveal({ key: 'aa' })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg)));
assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]);
});
});
test('reveal will return parents array for an element when hierarchy is loaded', () => {
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return testObject.$getChildren('treeDataProvider')
.then(() => testObject.$getChildren('treeDataProvider', '0/0:a'))
.then(() => treeView.reveal({ key: 'aa' })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg)));
assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]);
}));
});
test('reveal will return parents array for deeper element with no selection', () => {
tree = {
'b': {
'ba': {
'bac': {}
}
}
};
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return treeView.reveal({ key: 'bac' }, { select: false, focus: false, expand: false })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:b/0:ba/0:bac', label: { label: 'bac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b/0:ba' }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([
{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed },
{ handle: '0/0:b/0:ba', label: { label: 'ba' }, collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' }
], (<Array<any>>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg)));
assert.deepEqual({ select: false, focus: false, expand: false }, revealTarget.args[0][3]);
});
});
test('reveal after first udpate', () => {
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return loadCompleteTree('treeDataProvider')
.then(() => {
tree = {
'a': {
'aa': {},
'ac': {}
},
'b': {
'ba': {},
'bb': {}
}
};
onDidChangeTreeNode.fire(getNode('a'));
return treeView.reveal({ key: 'ac' })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:a/0:ac', label: { label: 'ac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg)));
assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]);
});
});
});
test('reveal after second udpate', () => {
const revealTarget = sinon.spy(target, '$reveal');
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription);
return loadCompleteTree('treeDataProvider')
.then(() => {
runWithEventMerging((resolve) => {
tree = {
'a': {
'aa': {},
'ac': {}
},
'b': {
'ba': {},
'bb': {}
}
};
onDidChangeTreeNode.fire(getNode('a'));
tree = {
'a': {
'aa': {},
'ac': {}
},
'b': {
'ba': {},
'bc': {}
}
};
onDidChangeTreeNode.fire(getNode('b'));
resolve();
}).then(() => {
return treeView.reveal({ key: 'bc' })
.then(() => {
assert.ok(revealTarget.calledOnce);
assert.deepEqual('treeDataProvider', revealTarget.args[0][0]);
assert.deepEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1]));
assert.deepEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg)));
assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]);
});
});
});
});
function loadCompleteTree(treeId: string, element?: string): Promise<null> {
return testObject.$getChildren(treeId, element)
.then(elements => elements.map(e => loadCompleteTree(treeId, e.handle)))
.then(() => null);
}
function removeUnsetKeys(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(o => removeUnsetKeys(o));
}
if (typeof obj === 'object') {
const result: { [key: string]: any } = {};
for (const key of Object.keys(obj)) {
if (obj[key] !== undefined) {
result[key] = removeUnsetKeys(obj[key]);
}
}
return result;
}
return obj;
}
function aNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {
return {
getChildren: (element: { key: string }): { key: string }[] => {
return getChildren(element ? element.key : undefined).map(key => getNode(key));
},
getTreeItem: (element: { key: string }): TreeItem => {
return getTreeItem(element.key);
},
onDidChangeTreeData: onDidChangeTreeNode.event
};
}
function aCompleteNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {
return {
getChildren: (element: { key: string }): { key: string }[] => {
return getChildren(element ? element.key : undefined).map(key => getNode(key));
},
getTreeItem: (element: { key: string }): TreeItem => {
return getTreeItem(element.key);
},
getParent: ({ key }: { key: string }): { key: string } | undefined => {
const parentKey = key.substring(0, key.length - 1);
return parentKey ? new Key(parentKey) : undefined;
},
onDidChangeTreeData: onDidChangeTreeNode.event
};
}
function aNodeWithIdTreeDataProvider(): TreeDataProvider<{ key: string }> {
return {
getChildren: (element: { key: string }): { key: string }[] => {
return getChildren(element ? element.key : undefined).map(key => getNode(key));
},
getTreeItem: (element: { key: string }): TreeItem => {
const treeItem = getTreeItem(element.key);
treeItem.id = element.key;
return treeItem;
},
onDidChangeTreeData: onDidChangeTreeNodeWithId.event
};
}
function aNodeWithHighlightedLabelTreeDataProvider(): TreeDataProvider<{ key: string }> {
return {
getChildren: (element: { key: string }): { key: string }[] => {
return getChildren(element ? element.key : undefined).map(key => getNode(key));
},
getTreeItem: (element: { key: string }): TreeItem => {
const treeItem = getTreeItem(element.key, [[0, 2], [3, 5]]);
treeItem.id = element.key;
return treeItem;
},
onDidChangeTreeData: onDidChangeTreeNodeWithId.event
};
}
function getTreeElement(element: string): any {
let parent = tree;
for (let i = 0; i < element.length; i++) {
parent = parent[element.substring(0, i + 1)];
if (!parent) {
return null;
}
}
return parent;
}
function getChildren(key: string | undefined): string[] {
if (!key) {
return Object.keys(tree);
}
let treeElement = getTreeElement(key);
if (treeElement) {
return Object.keys(treeElement);
}
return [];
}
function getTreeItem(key: string, highlights?: [number, number][]): TreeItem {
const treeElement = getTreeElement(key);
return {
label: <any>{ label: labels[key] || key, highlights },
collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None
};
}
function getNode(key: string): { key: string } {
if (!nodes[key]) {
nodes[key] = new Key(key);
}
return nodes[key];
}
class Key {
constructor(readonly key: string) { }
}
});

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { MarkdownString, LogLevel } from 'vs/workbench/api/common/extHostTypeConverters';
import { isEmptyObject } from 'vs/base/common/types';
import { forEach } from 'vs/base/common/collections';
import * as types from 'vs/workbench/api/common/extHostTypes';
import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log';
import { URI } from 'vs/base/common/uri';
suite('ExtHostTypeConverter', function () {
function size<T>(from: Record<any, any>): number {
let count = 0;
for (let key in from) {
if (Object.prototype.hasOwnProperty.call(from, key)) {
count += 1;
}
}
return count;
}
test('MarkdownConvert - uris', function () {
let data = MarkdownString.from('Hello');
assert.equal(isEmptyObject(data.uris), true);
assert.equal(data.value, 'Hello');
data = MarkdownString.from('Hello [link](foo)');
assert.equal(data.value, 'Hello [link](foo)');
assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri
data = MarkdownString.from('Hello [link](www.noscheme.bad)');
assert.equal(data.value, 'Hello [link](www.noscheme.bad)');
assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri
data = MarkdownString.from('Hello [link](foo:path)');
assert.equal(data.value, 'Hello [link](foo:path)');
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['foo:path']);
data = MarkdownString.from('hello@foo.bar');
assert.equal(data.value, 'hello@foo.bar');
assert.equal(size(data.uris!), 1);
// assert.ok(!!data.uris!['mailto:hello@foo.bar']);
data = MarkdownString.from('*hello* [click](command:me)');
assert.equal(data.value, '*hello* [click](command:me)');
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['command:me']);
data = MarkdownString.from('*hello* [click](file:///somepath/here). [click](file:///somepath/here)');
assert.equal(data.value, '*hello* [click](file:///somepath/here). [click](file:///somepath/here)');
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['file:///somepath/here']);
data = MarkdownString.from('*hello* [click](file:///somepath/here). [click](file:///somepath/here)');
assert.equal(data.value, '*hello* [click](file:///somepath/here). [click](file:///somepath/here)');
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['file:///somepath/here']);
data = MarkdownString.from('*hello* [click](file:///somepath/here). [click](file:///somepath/here2)');
assert.equal(data.value, '*hello* [click](file:///somepath/here). [click](file:///somepath/here2)');
assert.equal(size(data.uris!), 2);
assert.ok(!!data.uris!['file:///somepath/here']);
assert.ok(!!data.uris!['file:///somepath/here2']);
});
test('NPM script explorer running a script from the hover does not work #65561', function () {
let data = MarkdownString.from('*hello* [click](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2Ffoo%2Fbaz.ex%22%2C%22path%22%3A%22%2Fc%3A%2Ffoo%2Fbaz.ex%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22dev%22%7D)');
// assert that both uri get extracted but that the latter is only decoded once...
assert.equal(size(data.uris!), 2);
forEach(data.uris!, entry => {
if (entry.value.scheme === 'file') {
assert.ok(URI.revive(entry.value).toString().indexOf('file:///c%3A') === 0);
} else {
assert.equal(entry.value.scheme, 'command');
}
});
});
test('LogLevel', () => {
assert.equal(LogLevel.from(types.LogLevel.Error), _MainLogLevel.Error);
assert.equal(LogLevel.from(types.LogLevel.Info), _MainLogLevel.Info);
assert.equal(LogLevel.from(types.LogLevel.Off), _MainLogLevel.Off);
assert.equal(LogLevel.to(_MainLogLevel.Error), types.LogLevel.Error);
assert.equal(LogLevel.to(_MainLogLevel.Info), types.LogLevel.Info);
assert.equal(LogLevel.to(_MainLogLevel.Off), types.LogLevel.Off);
});
});

View File

@@ -0,0 +1,645 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import * as types from 'vs/workbench/api/common/extHostTypes';
import { isWindows } from 'vs/base/common/platform';
import { assertType } from 'vs/base/common/types';
function assertToJSON(a: any, expected: any) {
const raw = JSON.stringify(a);
const actual = JSON.parse(raw);
assert.deepEqual(actual, expected);
}
suite('ExtHostTypes', function () {
test('URI, toJSON', function () {
let uri = URI.parse('file:///path/test.file');
assert.deepEqual(uri.toJSON(), {
$mid: 1,
scheme: 'file',
path: '/path/test.file'
});
assert.ok(uri.fsPath);
assert.deepEqual(uri.toJSON(), {
$mid: 1,
scheme: 'file',
path: '/path/test.file',
fsPath: '/path/test.file'.replace(/\//g, isWindows ? '\\' : '/'),
_sep: isWindows ? 1 : undefined,
});
assert.ok(uri.toString());
assert.deepEqual(uri.toJSON(), {
$mid: 1,
scheme: 'file',
path: '/path/test.file',
fsPath: '/path/test.file'.replace(/\//g, isWindows ? '\\' : '/'),
_sep: isWindows ? 1 : undefined,
external: 'file:///path/test.file'
});
});
test('Disposable', () => {
let count = 0;
let d = new types.Disposable(() => {
count += 1;
return 12;
});
d.dispose();
assert.equal(count, 1);
d.dispose();
assert.equal(count, 1);
types.Disposable.from(undefined!, { dispose() { count += 1; } }).dispose();
assert.equal(count, 2);
assert.throws(() => {
new types.Disposable(() => {
throw new Error();
}).dispose();
});
new types.Disposable(undefined!).dispose();
});
test('Position', () => {
assert.throws(() => new types.Position(-1, 0));
assert.throws(() => new types.Position(0, -1));
let pos = new types.Position(0, 0);
assert.throws(() => (pos as any).line = -1);
assert.throws(() => (pos as any).character = -1);
assert.throws(() => (pos as any).line = 12);
let { line, character } = pos.toJSON();
assert.equal(line, 0);
assert.equal(character, 0);
});
test('Position, toJSON', function () {
let pos = new types.Position(4, 2);
assertToJSON(pos, { line: 4, character: 2 });
});
test('Position, isBefore(OrEqual)?', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
assert.ok(p1.isBeforeOrEqual(p1));
assert.ok(!p1.isBefore(p1));
assert.ok(p2.isBefore(p1));
assert.ok(p3.isBefore(p2));
});
test('Position, isAfter(OrEqual)?', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
assert.ok(p1.isAfterOrEqual(p1));
assert.ok(!p1.isAfter(p1));
assert.ok(p1.isAfter(p2));
assert.ok(p2.isAfter(p3));
assert.ok(p1.isAfter(p3));
});
test('Position, compareTo', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
assert.equal(p1.compareTo(p1), 0);
assert.equal(p2.compareTo(p1), -1);
assert.equal(p1.compareTo(p2), 1);
assert.equal(p2.compareTo(p3), 1);
assert.equal(p1.compareTo(p3), 1);
});
test('Position, translate', function () {
let p1 = new types.Position(1, 3);
assert.ok(p1.translate() === p1);
assert.ok(p1.translate({}) === p1);
assert.ok(p1.translate(0, 0) === p1);
assert.ok(p1.translate(0) === p1);
assert.ok(p1.translate(undefined, 0) === p1);
assert.ok(p1.translate(undefined) === p1);
let res = p1.translate(-1);
assert.equal(res.line, 0);
assert.equal(res.character, 3);
res = p1.translate({ lineDelta: -1 });
assert.equal(res.line, 0);
assert.equal(res.character, 3);
res = p1.translate(undefined, -1);
assert.equal(res.line, 1);
assert.equal(res.character, 2);
res = p1.translate({ characterDelta: -1 });
assert.equal(res.line, 1);
assert.equal(res.character, 2);
res = p1.translate(11);
assert.equal(res.line, 12);
assert.equal(res.character, 3);
assert.throws(() => p1.translate(null!));
assert.throws(() => p1.translate(null!, null!));
assert.throws(() => p1.translate(-2));
assert.throws(() => p1.translate({ lineDelta: -2 }));
assert.throws(() => p1.translate(-2, null!));
assert.throws(() => p1.translate(0, -4));
});
test('Position, with', function () {
let p1 = new types.Position(1, 3);
assert.ok(p1.with() === p1);
assert.ok(p1.with(1) === p1);
assert.ok(p1.with(undefined, 3) === p1);
assert.ok(p1.with(1, 3) === p1);
assert.ok(p1.with(undefined) === p1);
assert.ok(p1.with({ line: 1 }) === p1);
assert.ok(p1.with({ character: 3 }) === p1);
assert.ok(p1.with({ line: 1, character: 3 }) === p1);
let p2 = p1.with({ line: 0, character: 11 });
assert.equal(p2.line, 0);
assert.equal(p2.character, 11);
assert.throws(() => p1.with(null!));
assert.throws(() => p1.with(-9));
assert.throws(() => p1.with(0, -9));
assert.throws(() => p1.with({ line: -1 }));
assert.throws(() => p1.with({ character: -1 }));
});
test('Range', () => {
assert.throws(() => new types.Range(-1, 0, 0, 0));
assert.throws(() => new types.Range(0, -1, 0, 0));
assert.throws(() => new types.Range(new types.Position(0, 0), undefined!));
assert.throws(() => new types.Range(new types.Position(0, 0), null!));
assert.throws(() => new types.Range(undefined!, new types.Position(0, 0)));
assert.throws(() => new types.Range(null!, new types.Position(0, 0)));
let range = new types.Range(1, 0, 0, 0);
assert.throws(() => { (range as any).start = null; });
assert.throws(() => { (range as any).start = new types.Position(0, 3); });
});
test('Range, toJSON', function () {
let range = new types.Range(1, 2, 3, 4);
assertToJSON(range, [{ line: 1, character: 2 }, { line: 3, character: 4 }]);
});
test('Range, sorting', function () {
// sorts start/end
let range = new types.Range(1, 0, 0, 0);
assert.equal(range.start.line, 0);
assert.equal(range.end.line, 1);
range = new types.Range(0, 0, 1, 0);
assert.equal(range.start.line, 0);
assert.equal(range.end.line, 1);
});
test('Range, isEmpty|isSingleLine', function () {
let range = new types.Range(1, 0, 0, 0);
assert.ok(!range.isEmpty);
assert.ok(!range.isSingleLine);
range = new types.Range(1, 1, 1, 1);
assert.ok(range.isEmpty);
assert.ok(range.isSingleLine);
range = new types.Range(0, 1, 0, 11);
assert.ok(!range.isEmpty);
assert.ok(range.isSingleLine);
range = new types.Range(0, 0, 1, 1);
assert.ok(!range.isEmpty);
assert.ok(!range.isSingleLine);
});
test('Range, contains', function () {
let range = new types.Range(1, 1, 2, 11);
assert.ok(range.contains(range.start));
assert.ok(range.contains(range.end));
assert.ok(range.contains(range));
assert.ok(!range.contains(new types.Range(1, 0, 2, 11)));
assert.ok(!range.contains(new types.Range(0, 1, 2, 11)));
assert.ok(!range.contains(new types.Range(1, 1, 2, 12)));
assert.ok(!range.contains(new types.Range(1, 1, 3, 11)));
});
test('Range, intersection', function () {
let range = new types.Range(1, 1, 2, 11);
let res: types.Range;
res = range.intersection(range)!;
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 1);
assert.equal(res.end.line, 2);
assert.equal(res.end.character, 11);
res = range.intersection(new types.Range(2, 12, 4, 0))!;
assert.equal(res, undefined);
res = range.intersection(new types.Range(0, 0, 1, 0))!;
assert.equal(res, undefined);
res = range.intersection(new types.Range(0, 0, 1, 1))!;
assert.ok(res.isEmpty);
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 1);
res = range.intersection(new types.Range(2, 11, 61, 1))!;
assert.ok(res.isEmpty);
assert.equal(res.start.line, 2);
assert.equal(res.start.character, 11);
assert.throws(() => range.intersection(null!));
assert.throws(() => range.intersection(undefined!));
});
test('Range, union', function () {
let ran1 = new types.Range(0, 0, 5, 5);
assert.ok(ran1.union(new types.Range(0, 0, 1, 1)) === ran1);
let res: types.Range;
res = ran1.union(new types.Range(2, 2, 9, 9));
assert.ok(res.start === ran1.start);
assert.equal(res.end.line, 9);
assert.equal(res.end.character, 9);
ran1 = new types.Range(2, 1, 5, 3);
res = ran1.union(new types.Range(1, 0, 4, 2));
assert.ok(res.end === ran1.end);
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 0);
});
test('Range, with', function () {
let range = new types.Range(1, 1, 2, 11);
assert.ok(range.with(range.start) === range);
assert.ok(range.with(undefined, range.end) === range);
assert.ok(range.with(range.start, range.end) === range);
assert.ok(range.with(new types.Position(1, 1)) === range);
assert.ok(range.with(undefined, new types.Position(2, 11)) === range);
assert.ok(range.with() === range);
assert.ok(range.with({ start: range.start }) === range);
assert.ok(range.with({ start: new types.Position(1, 1) }) === range);
assert.ok(range.with({ end: range.end }) === range);
assert.ok(range.with({ end: new types.Position(2, 11) }) === range);
let res = range.with(undefined, new types.Position(9, 8));
assert.equal(res.end.line, 9);
assert.equal(res.end.character, 8);
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 1);
res = range.with({ end: new types.Position(9, 8) });
assert.equal(res.end.line, 9);
assert.equal(res.end.character, 8);
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 1);
res = range.with({ end: new types.Position(9, 8), start: new types.Position(2, 3) });
assert.equal(res.end.line, 9);
assert.equal(res.end.character, 8);
assert.equal(res.start.line, 2);
assert.equal(res.start.character, 3);
assert.throws(() => range.with(null!));
assert.throws(() => range.with(undefined, null!));
});
test('TextEdit', () => {
let range = new types.Range(1, 1, 2, 11);
let edit = new types.TextEdit(range, undefined!);
assert.equal(edit.newText, '');
assertToJSON(edit, { range: [{ line: 1, character: 1 }, { line: 2, character: 11 }], newText: '' });
edit = new types.TextEdit(range, null!);
assert.equal(edit.newText, '');
edit = new types.TextEdit(range, '');
assert.equal(edit.newText, '');
});
test('WorkspaceEdit', () => {
let a = URI.file('a.ts');
let b = URI.file('b.ts');
let edit = new types.WorkspaceEdit();
assert.ok(!edit.has(a));
edit.set(a, [types.TextEdit.insert(new types.Position(0, 0), 'fff')]);
assert.ok(edit.has(a));
assert.equal(edit.size, 1);
assertToJSON(edit, [[a.toJSON(), [{ range: [{ line: 0, character: 0 }, { line: 0, character: 0 }], newText: 'fff' }]]]);
edit.insert(b, new types.Position(1, 1), 'fff');
edit.delete(b, new types.Range(0, 0, 0, 0));
assert.ok(edit.has(b));
assert.equal(edit.size, 2);
assertToJSON(edit, [
[a.toJSON(), [{ range: [{ line: 0, character: 0 }, { line: 0, character: 0 }], newText: 'fff' }]],
[b.toJSON(), [{ range: [{ line: 1, character: 1 }, { line: 1, character: 1 }], newText: 'fff' }, { range: [{ line: 0, character: 0 }, { line: 0, character: 0 }], newText: '' }]]
]);
edit.set(b, undefined!);
assert.ok(!edit.has(b));
assert.equal(edit.size, 1);
edit.set(b, [types.TextEdit.insert(new types.Position(0, 0), 'ffff')]);
assert.equal(edit.get(b).length, 1);
});
test('WorkspaceEdit - keep order of text and file changes', function () {
const edit = new types.WorkspaceEdit();
edit.replace(URI.parse('foo:a'), new types.Range(1, 1, 1, 1), 'foo');
edit.renameFile(URI.parse('foo:a'), URI.parse('foo:b'));
edit.replace(URI.parse('foo:a'), new types.Range(2, 1, 2, 1), 'bar');
edit.replace(URI.parse('foo:b'), new types.Range(3, 1, 3, 1), 'bazz');
const all = edit._allEntries();
assert.equal(all.length, 4);
const [first, second, third, fourth] = all;
assertType(first._type === types.FileEditType.Text);
assert.equal(first.uri.toString(), 'foo:a');
assertType(second._type === types.FileEditType.File);
assert.equal(second.from!.toString(), 'foo:a');
assert.equal(second.to!.toString(), 'foo:b');
assertType(third._type === types.FileEditType.Text);
assert.equal(third.uri.toString(), 'foo:a');
assertType(fourth._type === types.FileEditType.Text);
assert.equal(fourth.uri.toString(), 'foo:b');
});
test('WorkspaceEdit - two edits for one resource', function () {
let edit = new types.WorkspaceEdit();
let uri = URI.parse('foo:bar');
edit.insert(uri, new types.Position(0, 0), 'Hello');
edit.insert(uri, new types.Position(0, 0), 'Foo');
assert.equal(edit._allEntries().length, 2);
let [first, second] = edit._allEntries();
assertType(first._type === types.FileEditType.Text);
assertType(second._type === types.FileEditType.Text);
assert.equal(first.edit.newText, 'Hello');
assert.equal(second.edit.newText, 'Foo');
});
test('DocumentLink', () => {
assert.throws(() => new types.DocumentLink(null!, null!));
assert.throws(() => new types.DocumentLink(new types.Range(1, 1, 1, 1), null!));
});
test('toJSON & stringify', function () {
assertToJSON(new types.Selection(3, 4, 2, 1), { start: { line: 2, character: 1 }, end: { line: 3, character: 4 }, anchor: { line: 3, character: 4 }, active: { line: 2, character: 1 } });
assertToJSON(new types.Location(URI.file('u.ts'), new types.Position(3, 4)), { uri: URI.parse('file:///u.ts').toJSON(), range: [{ line: 3, character: 4 }, { line: 3, character: 4 }] });
assertToJSON(new types.Location(URI.file('u.ts'), new types.Range(1, 2, 3, 4)), { uri: URI.parse('file:///u.ts').toJSON(), range: [{ line: 1, character: 2 }, { line: 3, character: 4 }] });
let diag = new types.Diagnostic(new types.Range(0, 1, 2, 3), 'hello');
assertToJSON(diag, { severity: 'Error', message: 'hello', range: [{ line: 0, character: 1 }, { line: 2, character: 3 }] });
diag.source = 'me';
assertToJSON(diag, { severity: 'Error', message: 'hello', range: [{ line: 0, character: 1 }, { line: 2, character: 3 }], source: 'me' });
assertToJSON(new types.DocumentHighlight(new types.Range(2, 3, 4, 5)), { range: [{ line: 2, character: 3 }, { line: 4, character: 5 }], kind: 'Text' });
assertToJSON(new types.DocumentHighlight(new types.Range(2, 3, 4, 5), types.DocumentHighlightKind.Read), { range: [{ line: 2, character: 3 }, { line: 4, character: 5 }], kind: 'Read' });
assertToJSON(new types.SymbolInformation('test', types.SymbolKind.Boolean, new types.Range(0, 1, 2, 3)), {
name: 'test',
kind: 'Boolean',
location: {
range: [{ line: 0, character: 1 }, { line: 2, character: 3 }]
}
});
assertToJSON(new types.CodeLens(new types.Range(7, 8, 9, 10)), { range: [{ line: 7, character: 8 }, { line: 9, character: 10 }] });
assertToJSON(new types.CodeLens(new types.Range(7, 8, 9, 10), { command: 'id', title: 'title' }), {
range: [{ line: 7, character: 8 }, { line: 9, character: 10 }],
command: { command: 'id', title: 'title' }
});
assertToJSON(new types.CompletionItem('complete'), { label: 'complete' });
let item = new types.CompletionItem('complete');
item.kind = types.CompletionItemKind.Interface;
assertToJSON(item, { label: 'complete', kind: 'Interface' });
});
test('SymbolInformation, old ctor', function () {
let info = new types.SymbolInformation('foo', types.SymbolKind.Array, new types.Range(1, 1, 2, 3));
assert.ok(info.location instanceof types.Location);
assert.equal(info.location.uri, undefined);
});
test('SnippetString, builder-methods', function () {
let string: types.SnippetString;
string = new types.SnippetString();
assert.equal(string.appendText('I need $ and $').value, 'I need \\$ and \\$');
string = new types.SnippetString();
assert.equal(string.appendText('I need \\$').value, 'I need \\\\\\$');
string = new types.SnippetString();
string.appendPlaceholder('fo$o}');
assert.equal(string.value, '${1:fo\\$o\\}}');
string = new types.SnippetString();
string.appendText('foo').appendTabstop(0).appendText('bar');
assert.equal(string.value, 'foo$0bar');
string = new types.SnippetString();
string.appendText('foo').appendTabstop().appendText('bar');
assert.equal(string.value, 'foo$1bar');
string = new types.SnippetString();
string.appendText('foo').appendTabstop(42).appendText('bar');
assert.equal(string.value, 'foo$42bar');
string = new types.SnippetString();
string.appendText('foo').appendPlaceholder('farboo').appendText('bar');
assert.equal(string.value, 'foo${1:farboo}bar');
string = new types.SnippetString();
string.appendText('foo').appendPlaceholder('far$boo').appendText('bar');
assert.equal(string.value, 'foo${1:far\\$boo}bar');
string = new types.SnippetString();
string.appendText('foo').appendPlaceholder(b => b.appendText('abc').appendPlaceholder('nested')).appendText('bar');
assert.equal(string.value, 'foo${1:abc${2:nested}}bar');
string = new types.SnippetString();
string.appendVariable('foo');
assert.equal(string.value, '${foo}');
string = new types.SnippetString();
string.appendText('foo').appendVariable('TM_SELECTED_TEXT').appendText('bar');
assert.equal(string.value, 'foo${TM_SELECTED_TEXT}bar');
string = new types.SnippetString();
string.appendVariable('BAR', b => b.appendPlaceholder('ops'));
assert.equal(string.value, '${BAR:${1:ops}}');
string = new types.SnippetString();
string.appendVariable('BAR', b => { });
assert.equal(string.value, '${BAR}');
string = new types.SnippetString();
string.appendChoice(['b', 'a', 'r']);
assert.equal(string.value, '${1|b,a,r|}');
string = new types.SnippetString();
string.appendChoice(['b,1', 'a,2', 'r,3']);
assert.equal(string.value, '${1|b\\,1,a\\,2,r\\,3|}');
string = new types.SnippetString();
string.appendChoice(['b', 'a', 'r'], 0);
assert.equal(string.value, '${0|b,a,r|}');
string = new types.SnippetString();
string.appendText('foo').appendChoice(['far', 'boo']).appendText('bar');
assert.equal(string.value, 'foo${1|far,boo|}bar');
string = new types.SnippetString();
string.appendText('foo').appendChoice(['far', '$boo']).appendText('bar');
assert.equal(string.value, 'foo${1|far,\\$boo|}bar');
string = new types.SnippetString();
string.appendText('foo').appendPlaceholder('farboo').appendChoice(['far', 'boo']).appendText('bar');
assert.equal(string.value, 'foo${1:farboo}${2|far,boo|}bar');
});
test('instanceof doesn\'t work for FileSystemError #49386', function () {
const error = types.FileSystemError.Unavailable('foo');
assert.ok(error instanceof Error);
assert.ok(error instanceof types.FileSystemError);
});
test('CodeActionKind contains', () => {
assert.ok(types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.RefactorExtract));
assert.ok(types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.RefactorExtract.append('other')));
assert.ok(!types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.Refactor));
assert.ok(!types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.Refactor.append('other')));
assert.ok(!types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.Empty.append('other').append('refactor')));
assert.ok(!types.CodeActionKind.RefactorExtract.contains(types.CodeActionKind.Empty.append('refactory')));
});
test('CodeActionKind intersects', () => {
assert.ok(types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.RefactorExtract));
assert.ok(types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Refactor));
assert.ok(types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.RefactorExtract.append('other')));
assert.ok(!types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Refactor.append('other')));
assert.ok(!types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Empty.append('other').append('refactor')));
assert.ok(!types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Empty.append('refactory')));
});
function toArr(uint32Arr: Uint32Array): number[] {
const r = [];
for (let i = 0, len = uint32Arr.length; i < len; i++) {
r[i] = uint32Arr[i];
}
return r;
}
test('SemanticTokensBuilder simple', () => {
const builder = new types.SemanticTokensBuilder();
builder.push(1, 0, 5, 1, 1);
builder.push(1, 10, 4, 2, 2);
builder.push(2, 2, 3, 2, 2);
assert.deepEqual(toArr(builder.build().data), [
1, 0, 5, 1, 1,
0, 10, 4, 2, 2,
1, 2, 3, 2, 2
]);
});
test('SemanticTokensBuilder no modifier', () => {
const builder = new types.SemanticTokensBuilder();
builder.push(1, 0, 5, 1);
builder.push(1, 10, 4, 2);
builder.push(2, 2, 3, 2);
assert.deepEqual(toArr(builder.build().data), [
1, 0, 5, 1, 0,
0, 10, 4, 2, 0,
1, 2, 3, 2, 0
]);
});
test('SemanticTokensBuilder out of order 1', () => {
const builder = new types.SemanticTokensBuilder();
builder.push(2, 0, 5, 1, 1);
builder.push(2, 10, 1, 2, 2);
builder.push(2, 15, 2, 3, 3);
builder.push(1, 0, 4, 4, 4);
assert.deepEqual(toArr(builder.build().data), [
1, 0, 4, 4, 4,
1, 0, 5, 1, 1,
0, 10, 1, 2, 2,
0, 5, 2, 3, 3
]);
});
test('SemanticTokensBuilder out of order 2', () => {
const builder = new types.SemanticTokensBuilder();
builder.push(2, 10, 5, 1, 1);
builder.push(2, 2, 4, 2, 2);
assert.deepEqual(toArr(builder.build().data), [
2, 2, 4, 2, 2,
0, 8, 5, 1, 1
]);
});
test('SemanticTokensBuilder with legend', () => {
const legend = new types.SemanticTokensLegend(
['aType', 'bType', 'cType', 'dType'],
['mod0', 'mod1', 'mod2', 'mod3', 'mod4', 'mod5']
);
const builder = new types.SemanticTokensBuilder(legend);
builder.push(new types.Range(1, 0, 1, 5), 'bType');
builder.push(new types.Range(2, 0, 2, 4), 'cType', ['mod0', 'mod5']);
builder.push(new types.Range(3, 0, 3, 3), 'dType', ['mod2', 'mod4']);
assert.deepEqual(toArr(builder.build().data), [
1, 0, 5, 1, 0,
1, 0, 4, 2, 1 | (1 << 5),
1, 0, 3, 3, (1 << 2) | (1 << 4)
]);
});
});

View File

@@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { NullLogService } from 'vs/platform/log/common/log';
import { MainThreadWebviewManager } from 'vs/workbench/api/browser/mainThreadWebviewManager';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import type * as vscode from 'vscode';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
suite('ExtHostWebview', () => {
let rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined;
setup(() => {
const shape = createNoopMainThreadWebviews();
rpcProtocol = SingleProxyRPCProtocol(shape);
});
test('Cannot register multiple serializers for the same view type', async () => {
const viewType = 'view.type';
const extHostWebviews = new ExtHostWebviews(rpcProtocol!, {
webviewCspSource: '',
webviewResourceRoot: '',
isExtensionDevelopmentDebug: false,
}, undefined, new NullLogService(), NullApiDeprecationService);
const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined);
let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined;
class NoopSerializer implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(_webview: vscode.WebviewPanel, _state: any): Promise<void> {
lastInvokedDeserializer = this;
}
}
const extension = {} as IExtensionDescription;
const serializerA = new NoopSerializer();
const serializerB = new NoopSerializer();
const serializerARegistration = extHostWebviewPanels.registerWebviewPanelSerializer(extension, viewType, serializerA);
await extHostWebviewPanels.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {});
assert.strictEqual(lastInvokedDeserializer, serializerA);
assert.throws(
() => extHostWebviewPanels.registerWebviewPanelSerializer(extension, viewType, serializerB),
'Should throw when registering two serializers for the same view');
serializerARegistration.dispose();
extHostWebviewPanels.registerWebviewPanelSerializer(extension, viewType, serializerB);
await extHostWebviewPanels.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {});
assert.strictEqual(lastInvokedDeserializer, serializerB);
});
test('asWebviewUri for desktop vscode-resource scheme', () => {
const extHostWebviews = new ExtHostWebviews(rpcProtocol!, {
webviewCspSource: '',
webviewResourceRoot: 'vscode-resource://{{resource}}',
isExtensionDevelopmentDebug: false,
}, undefined, new NullLogService(), NullApiDeprecationService);
const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined);
const webview = extHostWebviewPanels.createWebviewPanel({} as any, 'type', 'title', 1, {});
assert.strictEqual(
webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString(),
'vscode-resource://file///Users/codey/file.html',
'Unix basic'
);
assert.strictEqual(
webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString(),
'vscode-resource://file///Users/codey/file.html#frag',
'Unix should preserve fragment'
);
assert.strictEqual(
webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString(),
'vscode-resource://file///Users/codey/f%20ile.html',
'Unix with encoding'
);
assert.strictEqual(
webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString(),
'vscode-resource://file//localhost/Users/codey/file.html',
'Unix should preserve authority'
);
assert.strictEqual(
webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString(),
'vscode-resource://file///c%3A/codey/file.txt',
'Windows C drive'
);
});
test('asWebviewUri for web endpoint', () => {
const extHostWebviews = new ExtHostWebviews(rpcProtocol!, {
webviewCspSource: '',
webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`,
isExtensionDevelopmentDebug: false,
}, undefined, new NullLogService(), NullApiDeprecationService);
const extHostWebviewPanels = new ExtHostWebviewPanels(rpcProtocol!, extHostWebviews, undefined);
const webview = extHostWebviewPanels.createWebviewPanel({} as any, 'type', 'title', 1, {});
function stripEndpointUuid(input: string) {
return input.replace(/^https:\/\/[^\.]+?\./, '');
}
assert.strictEqual(
stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString()),
'webview.contoso.com/commit/file///Users/codey/file.html',
'Unix basic'
);
assert.strictEqual(
stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString()),
'webview.contoso.com/commit/file///Users/codey/file.html#frag',
'Unix should preserve fragment'
);
assert.strictEqual(
stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString()),
'webview.contoso.com/commit/file///Users/codey/f%20ile.html',
'Unix with encoding'
);
assert.strictEqual(
stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString()),
'webview.contoso.com/commit/file//localhost/Users/codey/file.html',
'Unix should preserve authority'
);
assert.strictEqual(
stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString()),
'webview.contoso.com/commit/file///c%3A/codey/file.txt',
'Windows C drive'
);
});
});
function createNoopMainThreadWebviews() {
return new class extends mock<MainThreadWebviewManager>() {
$createWebviewPanel() { /* noop */ }
$registerSerializer() { /* noop */ }
$unregisterSerializer() { /* noop */ }
};
}

View File

@@ -0,0 +1,787 @@
/*---------------------------------------------------------------------------------------------
* 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 { CancellationToken } from 'vs/base/common/cancellation';
import { basename } from 'vs/base/common/path';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace';
import { MainThreadWorkspace } from 'vs/workbench/api/browser/mainThreadWorkspace';
import { IMainContext, IWorkspaceData, MainContext, ITextSearchComplete } from 'vs/workbench/api/common/extHost.protocol';
import { RelativePattern } from 'vs/workbench/api/common/extHostTypes';
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { mock } from 'vs/base/test/common/mock';
import { TestRPCProtocol } from './testRPCProtocol';
import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IPatternInfo } from 'vs/workbench/services/search/common/search';
import { isWindows } from 'vs/base/common/platform';
function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace {
const result = new ExtHostWorkspace(
new ExtHostRpcService(mainContext),
new class extends mock<IExtHostInitDataService>() { workspace = data; },
logService
);
result.$initializeWorkspace(data);
return result;
}
suite('ExtHostWorkspace', function () {
const extensionDescriptor: IExtensionDescription = {
identifier: new ExtensionIdentifier('nullExtensionDescription'),
name: 'ext',
publisher: 'vscode',
enableProposedApi: false,
engines: undefined!,
extensionLocation: undefined!,
isBuiltin: false,
isUserBuiltin: false,
isUnderDevelopment: false,
version: undefined!
};
function assertAsRelativePath(workspace: ExtHostWorkspace, input: string, expected: string, includeWorkspace?: boolean) {
const actual = workspace.getRelativePath(input, includeWorkspace);
assert.equal(actual, expected);
}
test('asRelativePath', () => {
const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService());
assertAsRelativePath(ws, '/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot');
assertAsRelativePath(ws, '/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart',
'/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart');
assertAsRelativePath(ws, '', '');
assertAsRelativePath(ws, '/foo/bar', '/foo/bar');
assertAsRelativePath(ws, 'in/out', 'in/out');
});
test('asRelativePath, same paths, #11402', function () {
const root = '/home/aeschli/workspaces/samples/docker';
const input = '/home/aeschli/workspaces/samples/docker';
const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
assertAsRelativePath(ws, input, input);
const input2 = '/home/aeschli/workspaces/samples/docker/a.file';
assertAsRelativePath(ws, input2, 'a.file');
});
test('asRelativePath, no workspace', function () {
const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService());
assertAsRelativePath(ws, '', '');
assertAsRelativePath(ws, '/foo/bar', '/foo/bar');
});
test('asRelativePath, multiple folders', function () {
const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService());
assertAsRelativePath(ws, '/Coding/One/file.txt', 'One/file.txt');
assertAsRelativePath(ws, '/Coding/Two/files/out.txt', 'Two/files/out.txt');
assertAsRelativePath(ws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt');
});
test('slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', function () {
const mrws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService());
assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt');
assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt', true);
assertAsRelativePath(mrws, '/Coding/One/file.txt', 'file.txt', false);
assertAsRelativePath(mrws, '/Coding/Two/files/out.txt', 'Two/files/out.txt');
assertAsRelativePath(mrws, '/Coding/Two/files/out.txt', 'Two/files/out.txt', true);
assertAsRelativePath(mrws, '/Coding/Two/files/out.txt', 'files/out.txt', false);
assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt');
assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true);
assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false);
const srws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService());
assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt');
assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt', false);
assertAsRelativePath(srws, '/Coding/One/file.txt', 'One/file.txt', true);
assertAsRelativePath(srws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt');
assertAsRelativePath(srws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true);
assertAsRelativePath(srws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false);
});
test('getPath, legacy', function () {
let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService());
assert.equal(ws.getPath(), undefined);
ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService());
assert.equal(ws.getPath(), undefined);
ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService());
assert.equal(ws.getPath(), undefined);
ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService());
assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder');
ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService());
assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder');
});
test('WorkspaceFolder has name and index', function () {
const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService());
const [one, two] = ws.getWorkspaceFolders()!;
assert.equal(one.name, 'One');
assert.equal(one.index, 0);
assert.equal(two.name, 'Two');
assert.equal(two.index, 1);
});
test('getContainingWorkspaceFolder', () => {
const ws = createExtHostWorkspace(new TestRPCProtocol(), {
id: 'foo',
name: 'Test',
folders: [
aWorkspaceFolderData(URI.file('/Coding/One'), 0),
aWorkspaceFolderData(URI.file('/Coding/Two'), 1),
aWorkspaceFolderData(URI.file('/Coding/Two/Nested'), 2)
]
}, new NullLogService());
let folder = ws.getWorkspaceFolder(URI.file('/foo/bar'));
assert.equal(folder, undefined);
folder = ws.getWorkspaceFolder(URI.file('/Coding/One/file/path.txt'))!;
assert.equal(folder.name, 'One');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/file/path.txt'))!;
assert.equal(folder.name, 'Two');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nest'))!;
assert.equal(folder.name, 'Two');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/file'))!;
assert.equal(folder.name, 'Nested');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/f'))!;
assert.equal(folder.name, 'Nested');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested'), true)!;
assert.equal(folder.name, 'Two');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/'), true)!;
assert.equal(folder.name, 'Two');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested'))!;
assert.equal(folder.name, 'Nested');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/'))!;
assert.equal(folder.name, 'Nested');
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), true)!;
assert.equal(folder, undefined);
folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), false)!;
assert.equal(folder.name, 'Two');
});
test('Multiroot change event should have a delta, #29641', function (done) {
let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService());
let finished = false;
const finish = (error?: any) => {
if (!finished) {
finished = true;
done(error);
}
};
let sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.added, []);
assert.deepEqual(e.removed, []);
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
sub.dispose();
sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.removed, []);
assert.equal(e.added.length, 1);
assert.equal(e.added[0].uri.toString(), 'foo:bar');
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
sub.dispose();
sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.removed, []);
assert.equal(e.added.length, 1);
assert.equal(e.added[0].uri.toString(), 'foo:bar2');
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1)] });
sub.dispose();
sub = ws.onDidChangeWorkspace(e => {
try {
assert.equal(e.removed.length, 2);
assert.equal(e.removed[0].uri.toString(), 'foo:bar');
assert.equal(e.removed[1].uri.toString(), 'foo:bar2');
assert.equal(e.added.length, 1);
assert.equal(e.added[0].uri.toString(), 'foo:bar3');
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
sub.dispose();
finish();
});
test('Multiroot change keeps existing workspaces live', function () {
let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService());
let firstFolder = ws.getWorkspaceFolders()![0];
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar2'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1, 'renamed')] });
assert.equal(ws.getWorkspaceFolders()![1], firstFolder);
assert.equal(firstFolder.index, 1);
assert.equal(firstFolder.name, 'renamed');
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1), aWorkspaceFolderData(URI.parse('foo:bar'), 2)] });
assert.equal(ws.getWorkspaceFolders()![2], firstFolder);
assert.equal(firstFolder.index, 2);
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1)] });
assert.notEqual(firstFolder, ws.workspace!.folders[0]);
});
test('updateWorkspaceFolders - invalid arguments', function () {
let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService());
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, null!, null!));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 1));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 0));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, 0));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, -1));
ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService());
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 1));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2));
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 1, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
});
test('updateWorkspaceFolders - valid arguments', function (done) {
let finished = false;
const finish = (error?: any) => {
if (!finished) {
finished = true;
done(error);
}
};
const protocol: IMainContext = {
getProxy: () => { return undefined!; },
set: () => { return undefined!; },
assertRegistered: () => { },
drain: () => { return undefined!; },
};
const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService());
//
// Add one folder
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
assert.equal(1, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString());
const firstAddedFolder = ws.getWorkspaceFolders()![0];
let gotEvent = false;
let sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.removed, []);
assert.equal(e.added.length, 1);
assert.equal(e.added[0].uri.toString(), 'foo:bar');
assert.equal(e.added[0], firstAddedFolder); // verify object is still live
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live
//
// Add two more folders
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 1, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar1')), asUpdateWorkspaceFolderData(URI.parse('foo:bar2'))));
assert.equal(3, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
assert.equal(ws.workspace!.folders[2].uri.toString(), URI.parse('foo:bar2').toString());
const secondAddedFolder = ws.getWorkspaceFolders()![1];
const thirdAddedFolder = ws.getWorkspaceFolders()![2];
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.removed, []);
assert.equal(e.added.length, 2);
assert.equal(e.added[0].uri.toString(), 'foo:bar1');
assert.equal(e.added[1].uri.toString(), 'foo:bar2');
assert.equal(e.added[0], secondAddedFolder);
assert.equal(e.added[1], thirdAddedFolder);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1), aWorkspaceFolderData(URI.parse('foo:bar2'), 2)] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![2], thirdAddedFolder); // verify object is still live
//
// Remove one folder
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 1));
assert.equal(2, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.added, []);
assert.equal(e.removed.length, 1);
assert.equal(e.removed[0], thirdAddedFolder);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1)] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live
//
// Rename folder
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar'), 'renamed 1'), asUpdateWorkspaceFolderData(URI.parse('foo:bar1'), 'renamed 2')));
assert.equal(2, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
assert.equal(ws.workspace!.folders[0].name, 'renamed 1');
assert.equal(ws.workspace!.folders[1].name, 'renamed 2');
assert.equal(ws.getWorkspaceFolders()![0].name, 'renamed 1');
assert.equal(ws.getWorkspaceFolders()![1].name, 'renamed 2');
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.deepEqual(e.added, []);
assert.equal(e.removed.length, []);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0, 'renamed 1'), aWorkspaceFolderData(URI.parse('foo:bar1'), 1, 'renamed 2')] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live
assert.equal(ws.workspace!.folders[0].name, 'renamed 1');
assert.equal(ws.workspace!.folders[1].name, 'renamed 2');
assert.equal(ws.getWorkspaceFolders()![0].name, 'renamed 1');
assert.equal(ws.getWorkspaceFolders()![1].name, 'renamed 2');
//
// Add and remove folders
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar3')), asUpdateWorkspaceFolderData(URI.parse('foo:bar4'))));
assert.equal(2, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar3').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar4').toString());
const fourthAddedFolder = ws.getWorkspaceFolders()![0];
const fifthAddedFolder = ws.getWorkspaceFolders()![1];
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.equal(e.added.length, 2);
assert.equal(e.added[0], fourthAddedFolder);
assert.equal(e.added[1], fifthAddedFolder);
assert.equal(e.removed.length, 2);
assert.equal(e.removed[0], firstAddedFolder);
assert.equal(e.removed[1], secondAddedFolder);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar4'), 1)] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], fourthAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], fifthAddedFolder); // verify object is still live
//
// Swap folders
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar4')), asUpdateWorkspaceFolderData(URI.parse('foo:bar3'))));
assert.equal(2, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.equal(e.added.length, 0);
assert.equal(e.removed.length, 0);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar4'), 0), aWorkspaceFolderData(URI.parse('foo:bar3'), 1)] }); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live
assert.equal(fifthAddedFolder.index, 0);
assert.equal(fourthAddedFolder.index, 1);
//
// Add one folder after the other without waiting for confirmation (not supported currently)
//
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar5'))));
assert.equal(3, ws.workspace!.folders.length);
assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
assert.equal(ws.workspace!.folders[2].uri.toString(), URI.parse('foo:bar5').toString());
const sixthAddedFolder = ws.getWorkspaceFolders()![2];
gotEvent = false;
sub = ws.onDidChangeWorkspace(e => {
try {
assert.equal(e.added.length, 1);
assert.equal(e.added[0], sixthAddedFolder);
gotEvent = true;
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({
id: 'foo', name: 'Test', folders: [
aWorkspaceFolderData(URI.parse('foo:bar4'), 0),
aWorkspaceFolderData(URI.parse('foo:bar3'), 1),
aWorkspaceFolderData(URI.parse('foo:bar5'), 2)
]
}); // simulate acknowledgement from main side
assert.equal(gotEvent, true);
sub.dispose();
assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live
assert.equal(ws.getWorkspaceFolders()![2], sixthAddedFolder); // verify object is still live
finish();
});
test('Multiroot change event is immutable', function (done) {
let finished = false;
const finish = (error?: any) => {
if (!finished) {
finished = true;
done(error);
}
};
let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService());
let sub = ws.onDidChangeWorkspace(e => {
try {
assert.throws(() => {
(<any>e).added = [];
});
// assert.throws(() => {
// (<any>e.added)[0] = null;
// });
} catch (error) {
finish(error);
}
});
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
sub.dispose();
finish();
});
test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () {
if (isWindows) {
let ws = createExtHostWorkspace(new TestRPCProtocol(), {
id: 'foo', name: 'Test', folders: [
aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0)
]
}, new NullLogService());
assert.ok(ws.getWorkspaceFolder(URI.file('c:/Users/marek/Desktop/vsc_test/a.txt')));
assert.ok(ws.getWorkspaceFolder(URI.file('C:/Users/marek/Desktop/vsc_test/b.txt')));
}
});
function aWorkspaceFolderData(uri: URI, index: number, name: string = ''): IWorkspaceFolderData {
return {
uri,
index,
name: name || basename(uri.path)
};
}
function asUpdateWorkspaceFolderData(uri: URI, name?: string): { uri: URI, name?: string } {
return { uri, name };
}
test('findFiles - string include', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
$startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.equal(includePattern, 'foo');
assert.equal(_includeFolder, null);
assert.equal(excludePatternOrDisregardExcludes, null);
assert.equal(maxResults, 10);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles('foo', undefined, 10, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles - RelativePattern include', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
$startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.equal(includePattern, 'glob/**');
assert.deepEqual(_includeFolder, URI.file('/other/folder').toJSON());
assert.equal(excludePatternOrDisregardExcludes, null);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), undefined, 10, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles - no excludes', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
$startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.equal(includePattern, 'glob/**');
assert.deepEqual(_includeFolder, URI.file('/other/folder').toJSON());
assert.equal(excludePatternOrDisregardExcludes, false);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles - with cancelled token', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
$startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
const token = CancellationToken.Cancelled;
return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test'), token).then(() => {
assert(!mainThreadCalled, '!mainThreadCalled');
});
});
test('findFiles - RelativePattern exclude', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
$startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles('', new RelativePattern(root, 'glob/**'), 10, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findTextInFiles - no include', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
assert.equal(query.pattern, 'foo');
assert.equal(folder, null);
assert.equal(options.includePattern, null);
assert.equal(options.excludePattern, null);
return null;
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test'));
assert(mainThreadCalled, 'mainThreadCalled');
});
test('findTextInFiles - string include', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
assert.equal(query.pattern, 'foo');
assert.equal(folder, null);
assert.equal(options.includePattern, '**/files');
assert.equal(options.excludePattern, null);
return null;
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
await ws.findTextInFiles({ pattern: 'foo' }, { include: '**/files' }, () => { }, new ExtensionIdentifier('test'));
assert(mainThreadCalled, 'mainThreadCalled');
});
test('findTextInFiles - RelativePattern include', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
assert.equal(query.pattern, 'foo');
assert.deepEqual(folder, URI.file('/other/folder').toJSON());
assert.equal(options.includePattern, 'glob/**');
assert.equal(options.excludePattern, null);
return null;
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
await ws.findTextInFiles({ pattern: 'foo' }, { include: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test'));
assert(mainThreadCalled, 'mainThreadCalled');
});
test('findTextInFiles - with cancelled token', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
return null;
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
const token = CancellationToken.Cancelled;
await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test'), token);
assert(!mainThreadCalled, '!mainThreadCalled');
});
test('findTextInFiles - RelativePattern exclude', async () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null> {
mainThreadCalled = true;
assert.equal(query.pattern, 'foo');
assert.deepEqual(folder, null);
assert.equal(options.includePattern, null);
assert.equal(options.excludePattern, 'glob/**'); // exclude folder is ignored...
return null;
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
await ws.findTextInFiles({ pattern: 'foo' }, { exclude: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test'));
assert(mainThreadCalled, 'mainThreadCalled');
});
});

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* 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 { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { mock } from 'vs/base/test/common/mock';
suite('MainThreadCommands', function () {
test('dispose on unregister', function () {
const commands = new MainThreadCommands(SingleProxyRPCProtocol(null), undefined!, new class extends mock<IExtensionService>() { });
assert.equal(CommandsRegistry.getCommand('foo'), undefined);
// register
commands.$registerCommand('foo');
assert.ok(CommandsRegistry.getCommand('foo'));
// unregister
commands.$unregisterCommand('foo');
assert.equal(CommandsRegistry.getCommand('foo'), undefined);
});
test('unregister all on dispose', function () {
const commands = new MainThreadCommands(SingleProxyRPCProtocol(null), undefined!, new class extends mock<IExtensionService>() { });
assert.equal(CommandsRegistry.getCommand('foo'), undefined);
commands.$registerCommand('foo');
commands.$registerCommand('bar');
assert.ok(CommandsRegistry.getCommand('foo'));
assert.ok(CommandsRegistry.getCommand('bar'));
commands.dispose();
assert.equal(CommandsRegistry.getCommand('foo'), undefined);
assert.equal(CommandsRegistry.getCommand('bar'), undefined);
});
test('activate and throw when needed', async function () {
const activations: string[] = [];
const runs: string[] = [];
const commands = new MainThreadCommands(
SingleProxyRPCProtocol(null),
new class extends mock<ICommandService>() {
executeCommand<T>(id: string): Promise<T | undefined> {
runs.push(id);
return Promise.resolve(undefined);
}
},
new class extends mock<IExtensionService>() {
activateByEvent(id: string) {
activations.push(id);
return Promise.resolve();
}
}
);
// case 1: arguments and retry
try {
activations.length = 0;
await commands.$executeCommand('bazz', [1, 2, { n: 3 }], true);
assert.ok(false);
} catch (e) {
assert.deepEqual(activations, ['onCommand:bazz']);
assert.equal((<Error>e).message, '$executeCommand:retry');
}
// case 2: no arguments and retry
runs.length = 0;
await commands.$executeCommand('bazz', [], true);
assert.deepEqual(runs, ['bazz']);
// case 3: arguments and no retry
runs.length = 0;
await commands.$executeCommand('bazz', [1, 2, true], false);
assert.deepEqual(runs, ['bazz']);
});
});

View File

@@ -0,0 +1,232 @@
/*---------------------------------------------------------------------------------------------
* 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 sinon from 'sinon';
import { URI } from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { MainThreadConfiguration } from 'vs/workbench/api/browser/mainThreadConfiguration';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
suite('MainThreadConfiguration', function () {
let proxy = {
$initializeConfiguration: () => { }
};
let instantiationService: TestInstantiationService;
let target: sinon.SinonSpy;
suiteSetup(() => {
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': 'extHostConfiguration',
'title': 'a',
'type': 'object',
'properties': {
'extHostConfiguration.resource': {
'description': 'extHostConfiguration.resource',
'type': 'boolean',
'default': true,
'scope': ConfigurationScope.RESOURCE
},
'extHostConfiguration.window': {
'description': 'extHostConfiguration.resource',
'type': 'boolean',
'default': true,
'scope': ConfigurationScope.WINDOW
}
}
});
});
setup(() => {
target = sinon.spy();
instantiationService = new TestInstantiationService();
instantiationService.stub(IConfigurationService, WorkspaceService);
instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', sinon.mock());
instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', sinon.mock());
instantiationService.stub(IConfigurationService, 'updateValue', target);
instantiationService.stub(IEnvironmentService, {
isBuilt: false
});
});
test('update resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update resource configuration without configuration target defaults to folder', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]);
});
test('update configuration with user configuration target', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.USER, target.args[0][3]);
});
test('update configuration with workspace configuration target', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('update configuration with folder configuration target', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]);
});
test('remove resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.FOLDER });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined);
assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]);
});
test('remove configuration without configuration target defaults to folder', function () {
instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{ getWorkbenchState: () => WorkbenchState.WORKSPACE });
const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy));
testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined);
assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]);
});
});

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* 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 { MarkerService } from 'vs/platform/markers/common/markerService';
import { MainThreadDiagnostics } from 'vs/workbench/api/browser/mainThreadDiagnostics';
import { URI } from 'vs/base/common/uri';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/workbench/test/common/workbenchTestServices';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
suite('MainThreadDiagnostics', function () {
let markerService: MarkerService;
setup(function () {
markerService = new MarkerService();
});
test('clear markers on dispose', function () {
let diag = new MainThreadDiagnostics(
new class implements IExtHostContext {
remoteAuthority = '';
assertRegistered() { }
set(v: any): any { return null; }
getProxy(): any {
return {
$acceptMarkersChange() { }
};
}
drain(): any { return null; }
},
markerService,
new class extends mock<IUriIdentityService>() {
asCanonicalUri(uri: URI) { return uri; }
}
);
diag.$changeMany('foo', [[URI.file('a'), [{
code: '666',
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
message: 'fffff',
severity: 1,
source: 'me'
}]]]);
assert.equal(markerService.read().length, 1);
diag.dispose();
assert.equal(markerService.read().length, 0);
});
});

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { MainThreadDocumentContentProviders } from 'vs/workbench/api/browser/mainThreadDocumentContentProviders';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { mock } from 'vs/base/test/common/mock';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { TextEdit } from 'vs/editor/common/modes';
suite('MainThreadDocumentContentProviders', function () {
test('events are processed properly', function () {
let uri = URI.parse('test:uri');
let model = createTextModel('1', undefined, undefined, uri);
let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null!, null!,
new class extends mock<IModelService>() {
getModel(_uri: URI) {
assert.equal(uri.toString(), _uri.toString());
return model;
}
},
new class extends mock<IEditorWorkerService>() {
computeMoreMinimalEdits(_uri: URI, data: TextEdit[] | undefined) {
assert.equal(model.getValue(), '1');
return Promise.resolve(data);
}
},
);
return new Promise<void>((resolve, reject) => {
let expectedEvents = 1;
model.onDidChangeContent(e => {
expectedEvents -= 1;
try {
assert.ok(expectedEvents >= 0);
} catch (err) {
reject(err);
}
if (model.getValue() === '1\n2\n3') {
resolve();
}
});
providers.$onVirtualDocumentChange(uri, '1\n2');
providers.$onVirtualDocumentChange(uri, '1\n2\n3');
});
});
});

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { timeout } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
import { extUri } from 'vs/base/common/resources';
suite('BoundModelReferenceCollection', () => {
let col = new BoundModelReferenceCollection(extUri, 15, 75);
teardown(() => {
col.dispose();
});
test('max age', async () => {
let didDispose = false;
col.add(
URI.parse('test://farboo'),
{
object: <any>{ textEditorModel: createTextModel('farboo') },
dispose() {
didDispose = true;
}
});
await timeout(30);
assert.equal(didDispose, true);
});
test('max size', () => {
let disposed: number[] = [];
col.add(
URI.parse('test://farboo'),
{
object: <any>{ textEditorModel: createTextModel('farboo') },
dispose() {
disposed.push(0);
}
});
col.add(
URI.parse('test://boofar'),
{
object: <any>{ textEditorModel: createTextModel('boofar') },
dispose() {
disposed.push(1);
}
});
col.add(
URI.parse('test://xxxxxxx'),
{
object: <any>{ textEditorModel: createTextModel(new Array(71).join('x')) },
dispose() {
disposed.push(2);
}
});
assert.deepEqual(disposed, [0, 1]);
});
test('dispose uri', () => {
let disposed: number[] = [];
col.add(
URI.parse('test:///farboo'),
{
object: <any>{ textEditorModel: createTextModel('farboo') },
dispose() {
disposed.push(0);
}
});
col.add(
URI.parse('test:///boofar'),
{
object: <any>{ textEditorModel: createTextModel('boofar') },
dispose() {
disposed.push(1);
}
});
col.add(
URI.parse('test:///boo/far1'),
{
object: <any>{ textEditorModel: createTextModel('boo/far1') },
dispose() {
disposed.push(2);
}
});
col.add(
URI.parse('test:///boo/far2'),
{
object: <any>{ textEditorModel: createTextModel('boo/far2') },
dispose() {
disposed.push(3);
}
});
col.add(
URI.parse('test:///boo1/far'),
{
object: <any>{ textEditorModel: createTextModel('boo1/far') },
dispose() {
disposed.push(4);
}
});
col.remove(URI.parse('test:///unknown'));
assert.equal(disposed.length, 0);
col.remove(URI.parse('test:///farboo'));
assert.deepEqual(disposed, [0]);
disposed = [];
col.remove(URI.parse('test:///boo'));
assert.deepEqual(disposed, [2, 3]);
});
});

View File

@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* 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 { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors';
import { SingleProxyRPCProtocol } from './testRPCProtocol';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { mock } from 'vs/base/test/common/mock';
import { TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices';
import { Event } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IFileService } from 'vs/platform/files/common/files';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { NullLogService } from 'vs/platform/log/common/log';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { TestTextResourcePropertiesService, TestWorkingCopyFileService } from 'vs/workbench/test/common/workbenchTestServices';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
suite('MainThreadDocumentsAndEditors', () => {
let modelService: ModelServiceImpl;
let codeEditorService: TestCodeEditorService;
let textFileService: ITextFileService;
let deltas: IDocumentsAndEditorsDelta[] = [];
const hugeModelString = new Array(2 + (50 * 1024 * 1024)).join('-');
function myCreateTestCodeEditor(model: ITextModel | undefined): ITestCodeEditor {
return createTestCodeEditor({
model: model,
serviceCollection: new ServiceCollection(
[ICodeEditorService, codeEditorService]
)
});
}
setup(() => {
deltas.length = 0;
const configService = new TestConfigurationService();
configService.setUserConfiguration('editor', { 'detectIndentation': false });
const dialogService = new TestDialogService();
const notificationService = new TestNotificationService();
const undoRedoService = new UndoRedoService(dialogService, notificationService);
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService);
codeEditorService = new TestCodeEditorService();
textFileService = new class extends mock<ITextFileService>() {
isDirty() { return false; }
files = <any>{
onDidSave: Event.None,
onDidRevert: Event.None,
onDidChangeDirty: Event.None
};
};
const workbenchEditorService = new TestEditorService();
const editorGroupService = new TestEditorGroupsService();
const fileService = new class extends mock<IFileService>() {
onDidRunOperation = Event.None;
onDidChangeFileSystemProviderCapabilities = Event.None;
onDidChangeFileSystemProviderRegistrations = Event.None;
};
new MainThreadDocumentsAndEditors(
SingleProxyRPCProtocol(new class extends mock<ExtHostDocumentsAndEditorsShape>() {
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta) { deltas.push(delta); }
}),
modelService,
textFileService,
workbenchEditorService,
codeEditorService,
fileService,
null!,
editorGroupService,
null!,
new class extends mock<IPanelService>() implements IPanelService {
declare readonly _serviceBrand: undefined;
onDidPanelOpen = Event.None;
onDidPanelClose = Event.None;
getActivePanel() {
return undefined;
}
},
TestEnvironmentService,
new TestWorkingCopyFileService(),
new UriIdentityService(fileService),
new class extends mock<IClipboardService>() {
readText() {
return Promise.resolve('clipboard_contents');
}
},
new TestPathService()
);
});
test('Model#add', () => {
deltas.length = 0;
modelService.createModel('farboo', null);
assert.equal(deltas.length, 1);
const [delta] = deltas;
assert.equal(delta.addedDocuments!.length, 1);
assert.equal(delta.removedDocuments, undefined);
assert.equal(delta.addedEditors, undefined);
assert.equal(delta.removedEditors, undefined);
assert.equal(delta.newActiveEditor, null);
});
test('ignore huge model', function () {
this.timeout(1000 * 60); // increase timeout for this one test
const model = modelService.createModel(hugeModelString, null);
assert.ok(model.isTooLargeForSyncing());
assert.equal(deltas.length, 1);
const [delta] = deltas;
assert.equal(delta.newActiveEditor, null);
assert.equal(delta.addedDocuments, undefined);
assert.equal(delta.removedDocuments, undefined);
assert.equal(delta.addedEditors, undefined);
assert.equal(delta.removedEditors, undefined);
});
test('ignore simple widget model', function () {
this.timeout(1000 * 60); // increase timeout for this one test
const model = modelService.createModel('test', null, undefined, true);
assert.ok(model.isForSimpleWidget);
assert.equal(deltas.length, 1);
const [delta] = deltas;
assert.equal(delta.newActiveEditor, null);
assert.equal(delta.addedDocuments, undefined);
assert.equal(delta.removedDocuments, undefined);
assert.equal(delta.addedEditors, undefined);
assert.equal(delta.removedEditors, undefined);
});
test('ignore huge model from editor', function () {
this.timeout(1000 * 60); // increase timeout for this one test
const model = modelService.createModel(hugeModelString, null);
const editor = myCreateTestCodeEditor(model);
assert.equal(deltas.length, 1);
deltas.length = 0;
assert.equal(deltas.length, 0);
editor.dispose();
});
test('ignore editor w/o model', () => {
const editor = myCreateTestCodeEditor(undefined);
assert.equal(deltas.length, 1);
const [delta] = deltas;
assert.equal(delta.newActiveEditor, null);
assert.equal(delta.addedDocuments, undefined);
assert.equal(delta.removedDocuments, undefined);
assert.equal(delta.addedEditors, undefined);
assert.equal(delta.removedEditors, undefined);
editor.dispose();
});
test('editor with model', () => {
deltas.length = 0;
const model = modelService.createModel('farboo', null);
const editor = myCreateTestCodeEditor(model);
assert.equal(deltas.length, 2);
const [first, second] = deltas;
assert.equal(first.addedDocuments!.length, 1);
assert.equal(first.newActiveEditor, null);
assert.equal(first.removedDocuments, undefined);
assert.equal(first.addedEditors, undefined);
assert.equal(first.removedEditors, undefined);
assert.equal(second.addedEditors!.length, 1);
assert.equal(second.addedDocuments, undefined);
assert.equal(second.removedDocuments, undefined);
assert.equal(second.removedEditors, undefined);
assert.equal(second.newActiveEditor, undefined);
editor.dispose();
});
test('editor with dispos-ed/-ing model', () => {
modelService.createModel('foobar', null);
const model = modelService.createModel('farboo', null);
const editor = myCreateTestCodeEditor(model);
// ignore things until now
deltas.length = 0;
modelService.destroyModel(model.uri);
assert.equal(deltas.length, 1);
const [first] = deltas;
assert.equal(first.newActiveEditor, null);
assert.equal(first.removedEditors!.length, 1);
assert.equal(first.removedDocuments!.length, 1);
assert.equal(first.addedDocuments, undefined);
assert.equal(first.addedEditors, undefined);
editor.dispose();
});
});

View File

@@ -0,0 +1,238 @@
/*---------------------------------------------------------------------------------------------
* 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 { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors';
import { SingleProxyRPCProtocol, TestRPCProtocol } from './testRPCProtocol';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ExtHostDocumentsAndEditorsShape, ExtHostContext, ExtHostDocumentsShape, IWorkspaceTextEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/base/test/common/mock';
import { Event } from 'vs/base/common/event';
import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditors';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { IModelService } from 'vs/editor/common/services/modelService';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
import { BulkEditService } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditService';
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IReference, ImmortalReference } from 'vs/base/common/lifecycle';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IFileService } from 'vs/platform/files/common/files';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { extUri } from 'vs/base/common/resources';
suite('MainThreadEditors', () => {
const resource = URI.parse('foo:bar');
let modelService: IModelService;
let editors: MainThreadTextEditors;
const movedResources = new Map<URI, URI>();
const copiedResources = new Map<URI, URI>();
const createdResources = new Set<URI>();
const deletedResources = new Set<URI>();
setup(() => {
movedResources.clear();
copiedResources.clear();
createdResources.clear();
deletedResources.clear();
const configService = new TestConfigurationService();
const dialogService = new TestDialogService();
const notificationService = new TestNotificationService();
const undoRedoService = new UndoRedoService(dialogService, notificationService);
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService);
const services = new ServiceCollection();
services.set(IBulkEditService, new SyncDescriptor(BulkEditService));
services.set(ILabelService, new SyncDescriptor(LabelService));
services.set(ILogService, new NullLogService());
services.set(IWorkspaceContextService, new TestContextService());
services.set(IWorkbenchEnvironmentService, TestEnvironmentService);
services.set(IConfigurationService, configService);
services.set(IDialogService, dialogService);
services.set(INotificationService, notificationService);
services.set(IUndoRedoService, undoRedoService);
services.set(IModelService, modelService);
services.set(ICodeEditorService, new TestCodeEditorService());
services.set(IFileService, new TestFileService());
services.set(IEditorService, new TestEditorService());
services.set(IEditorGroupsService, new TestEditorGroupsService());
services.set(ITextFileService, new class extends mock<ITextFileService>() {
isDirty() { return false; }
files = <any>{
onDidSave: Event.None,
onDidRevert: Event.None,
onDidChangeDirty: Event.None
};
});
services.set(IWorkingCopyFileService, new class extends mock<IWorkingCopyFileService>() {
onDidRunWorkingCopyFileOperation = Event.None;
create(resource: URI) {
createdResources.add(resource);
return Promise.resolve(Object.create(null));
}
move(files: { source: URI, target: URI }[]) {
const { source, target } = files[0];
movedResources.set(source, target);
return Promise.resolve(Object.create(null));
}
copy(files: { source: URI, target: URI }[]) {
const { source, target } = files[0];
copiedResources.set(source, target);
return Promise.resolve(Object.create(null));
}
delete(resources: URI[]) {
for (const resource of resources) {
deletedResources.add(resource);
}
return Promise.resolve(undefined);
}
});
services.set(ITextModelService, new class extends mock<ITextModelService>() {
createModelReference(resource: URI): Promise<IReference<IResolvedTextEditorModel>> {
const textEditorModel = new class extends mock<IResolvedTextEditorModel>() {
textEditorModel = modelService.getModel(resource)!;
};
textEditorModel.isReadonly = () => false;
return Promise.resolve(new ImmortalReference(textEditorModel));
}
});
services.set(IEditorWorkerService, new class extends mock<IEditorWorkerService>() {
});
services.set(IPanelService, new class extends mock<IPanelService>() implements IPanelService {
declare readonly _serviceBrand: undefined;
onDidPanelOpen = Event.None;
onDidPanelClose = Event.None;
getActivePanel() {
return undefined;
}
});
services.set(IUriIdentityService, new class extends mock<IUriIdentityService>() {
get extUri() { return extUri; }
});
const instaService = new InstantiationService(services);
const rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock<ExtHostDocumentsShape>() {
$acceptModelChanged(): void {
}
});
rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new class extends mock<ExtHostDocumentsAndEditorsShape>() {
$acceptDocumentsAndEditorsDelta(): void {
}
});
const documentAndEditor = instaService.createInstance(MainThreadDocumentsAndEditors, rpcProtocol);
editors = instaService.createInstance(MainThreadTextEditors, documentAndEditor, SingleProxyRPCProtocol(null));
});
test(`applyWorkspaceEdit returns false if model is changed by user`, () => {
let model = modelService.createModel('something', null, resource);
let workspaceResourceEdit: IWorkspaceTextEditDto = {
_type: WorkspaceEditType.Text,
resource: resource,
modelVersionId: model.getVersionId(),
edit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
};
// Act as if the user edited the model
model.applyEdits([EditOperation.insert(new Position(0, 0), 'something')]);
return editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit] }).then((result) => {
assert.equal(result, false);
});
});
test(`issue #54773: applyWorkspaceEdit checks model version in race situation`, () => {
let model = modelService.createModel('something', null, resource);
let workspaceResourceEdit1: IWorkspaceTextEditDto = {
_type: WorkspaceEditType.Text,
resource: resource,
modelVersionId: model.getVersionId(),
edit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
};
let workspaceResourceEdit2: IWorkspaceTextEditDto = {
_type: WorkspaceEditType.Text,
resource: resource,
modelVersionId: model.getVersionId(),
edit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
};
let p1 = editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit1] }).then((result) => {
// first edit request succeeds
assert.equal(result, true);
});
let p2 = editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit2] }).then((result) => {
// second edit request fails
assert.equal(result, false);
});
return Promise.all([p1, p2]);
});
test(`applyWorkspaceEdit with only resource edit`, () => {
return editors.$tryApplyWorkspaceEdit({
edits: [
{ _type: WorkspaceEditType.File, oldUri: resource, newUri: resource, options: undefined },
{ _type: WorkspaceEditType.File, oldUri: undefined, newUri: resource, options: undefined },
{ _type: WorkspaceEditType.File, oldUri: resource, newUri: undefined, options: undefined }
]
}).then((result) => {
assert.equal(result, true);
assert.equal(movedResources.get(resource), resource);
assert.equal(createdResources.has(resource), true);
assert.equal(deletedResources.has(resource), true);
});
});
});

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* 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 { ExtHostTreeViewsShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/base/test/common/mock';
import { ITreeItem, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, ITreeViewDescriptor, ITreeView, ViewContainer, IViewDescriptorService, TreeItemCollapsibleState } from 'vs/workbench/common/views';
import { NullLogService } from 'vs/platform/log/common/log';
import { MainThreadTreeViews } from 'vs/workbench/api/browser/mainThreadTreeViews';
import { TestViewsService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService';
suite('MainThreadHostTreeView', function () {
const testTreeViewId = 'testTreeView';
const customValue = 'customValue';
const ViewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
interface CustomTreeItem extends ITreeItem {
customProp: string;
}
class MockExtHostTreeViewsShape extends mock<ExtHostTreeViewsShape>() {
async $getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]> {
return [<CustomTreeItem>{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }];
}
async $hasResolve(): Promise<boolean> {
return false;
}
$setVisible(): void { }
}
let container: ViewContainer;
let mainThreadTreeViews: MainThreadTreeViews;
let extHostTreeViewsShape: MockExtHostTreeViewsShape;
setup(async () => {
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
const viewDescriptorService = instantiationService.createInstance(ViewDescriptorService);
instantiationService.stub(IViewDescriptorService, viewDescriptorService);
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const viewDescriptor: ITreeViewDescriptor = {
id: testTreeViewId,
ctorDescriptor: null!,
name: 'Test View 1',
treeView: instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title'),
};
ViewsRegistry.registerViews([viewDescriptor], container);
const testExtensionService = new TestExtensionService();
extHostTreeViewsShape = new MockExtHostTreeViewsShape();
mainThreadTreeViews = new MainThreadTreeViews(
new class implements IExtHostContext {
remoteAuthority = '';
assertRegistered() { }
set(v: any): any { return null; }
getProxy(): any {
return extHostTreeViewsShape;
}
drain(): any { return null; }
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false });
await testExtensionService.whenInstalledExtensionsRegistered();
});
teardown(() => {
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container);
});
test('getChildren keeps custom properties', async () => {
const treeView: ITreeView = (<ITreeViewDescriptor>ViewsRegistry.getView(testTreeViewId)).treeView;
const children = await treeView.dataProvider?.getChildren({ handle: 'root', collapsibleState: TreeItemCollapsibleState.Expanded });
assert(children!.length === 1, 'Exactly one child should be returned');
assert((<CustomTreeItem>children![0]).customProp === customValue, 'Tree Items should keep custom properties');
});
});

View File

@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { CharCode } from 'vs/base/common/charCode';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { isThenable } from 'vs/base/common/async';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
export function SingleProxyRPCProtocol(thing: any): IExtHostContext & IExtHostRpcService {
return {
_serviceBrand: undefined,
remoteAuthority: null!,
getProxy<T>(): T {
return thing;
},
set<T, R extends T>(identifier: ProxyIdentifier<T>, value: R): R {
return value;
},
assertRegistered: undefined!,
drain: undefined!
};
}
export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService {
public _serviceBrand: undefined;
public remoteAuthority = null!;
private _callCountValue: number = 0;
private _idle?: Promise<any>;
private _completeIdle?: Function;
private readonly _locals: { [id: string]: any; };
private readonly _proxies: { [id: string]: any; };
constructor() {
this._locals = Object.create(null);
this._proxies = Object.create(null);
}
drain(): Promise<void> {
return Promise.resolve();
}
private get _callCount(): number {
return this._callCountValue;
}
private set _callCount(value: number) {
this._callCountValue = value;
if (this._callCountValue === 0) {
if (this._completeIdle) {
this._completeIdle();
}
this._idle = undefined;
}
}
sync(): Promise<any> {
return new Promise<any>((c) => {
setTimeout(c, 0);
}).then(() => {
if (this._callCount === 0) {
return undefined;
}
if (!this._idle) {
this._idle = new Promise<any>((c, e) => {
this._completeIdle = c;
});
}
return this._idle;
});
}
public getProxy<T>(identifier: ProxyIdentifier<T>): T {
if (!this._proxies[identifier.sid]) {
this._proxies[identifier.sid] = this._createProxy(identifier.sid);
}
return this._proxies[identifier.sid];
}
private _createProxy<T>(proxyId: string): T {
let handler = {
get: (target: any, name: PropertyKey) => {
if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) {
target[name] = (...myArgs: any[]) => {
return this._remoteCall(proxyId, name, myArgs);
};
}
return target[name];
}
};
return new Proxy(Object.create(null), handler);
}
public set<T, R extends T>(identifier: ProxyIdentifier<T>, value: R): R {
this._locals[identifier.sid] = value;
return value;
}
protected _remoteCall(proxyId: string, path: string, args: any[]): Promise<any> {
this._callCount++;
return new Promise<any>((c) => {
setTimeout(c, 0);
}).then(() => {
const instance = this._locals[proxyId];
// pretend the args went over the wire... (invoke .toJSON on objects...)
const wireArgs = simulateWireTransfer(args);
let p: Promise<any>;
try {
let result = (<Function>instance[path]).apply(instance, wireArgs);
p = isThenable(result) ? result : Promise.resolve(result);
} catch (err) {
p = Promise.reject(err);
}
return p.then(result => {
this._callCount--;
// pretend the result went over the wire... (invoke .toJSON on objects...)
const wireResult = simulateWireTransfer(result);
return wireResult;
}, err => {
this._callCount--;
return Promise.reject(err);
});
});
}
public assertRegistered(identifiers: ProxyIdentifier<any>[]): void {
throw new Error('Not implemented!');
}
}
function simulateWireTransfer<T>(obj: T): T {
if (!obj) {
return obj;
}
return JSON.parse(JSON.stringify(obj));
}

View File

@@ -0,0 +1,175 @@
/*---------------------------------------------------------------------------------------------
* 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 { Part } from 'vs/workbench/browser/part';
import * as Types from 'vs/base/common/types';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { append, $, hide } from 'vs/base/browser/dom';
import { TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices';
import { StorageScope } from 'vs/platform/storage/common/storage';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
class SimplePart extends Part {
minimumWidth: number = 50;
maximumWidth: number = 50;
minimumHeight: number = 50;
maximumHeight: number = 50;
layout(width: number, height: number): void {
throw new Error('Method not implemented.');
}
toJSON(): object {
throw new Error('Method not implemented.');
}
}
class MyPart extends SimplePart {
constructor(private expectedParent: HTMLElement) {
super('myPart', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService());
}
createTitleArea(parent: HTMLElement): HTMLElement {
assert.strictEqual(parent, this.expectedParent);
return super.createTitleArea(parent)!;
}
createContentArea(parent: HTMLElement): HTMLElement {
assert.strictEqual(parent, this.expectedParent);
return super.createContentArea(parent)!;
}
getMemento(scope: StorageScope) {
return super.getMemento(scope);
}
saveState(): void {
return super.saveState();
}
}
class MyPart2 extends SimplePart {
constructor() {
super('myPart2', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService());
}
createTitleArea(parent: HTMLElement): HTMLElement {
const titleContainer = append(parent, $('div'));
const titleLabel = append(titleContainer, $('span'));
titleLabel.id = 'myPart.title';
titleLabel.innerText = 'Title';
return titleContainer;
}
createContentArea(parent: HTMLElement): HTMLElement {
const contentContainer = append(parent, $('div'));
const contentSpan = append(contentContainer, $('span'));
contentSpan.id = 'myPart.content';
contentSpan.innerText = 'Content';
return contentContainer;
}
}
class MyPart3 extends SimplePart {
constructor() {
super('myPart2', { hasTitle: false }, new TestThemeService(), new TestStorageService(), new TestLayoutService());
}
createTitleArea(parent: HTMLElement): HTMLElement {
return null!;
}
createContentArea(parent: HTMLElement): HTMLElement {
const contentContainer = append(parent, $('div'));
const contentSpan = append(contentContainer, $('span'));
contentSpan.id = 'myPart.content';
contentSpan.innerText = 'Content';
return contentContainer;
}
}
suite('Workbench parts', () => {
let fixture: HTMLElement;
let fixtureId = 'workbench-part-fixture';
setup(() => {
fixture = document.createElement('div');
fixture.id = fixtureId;
document.body.appendChild(fixture);
});
teardown(() => {
document.body.removeChild(fixture);
});
test('Creation', () => {
let b = document.createElement('div');
document.getElementById(fixtureId)!.appendChild(b);
hide(b);
let part = new MyPart(b);
part.create(b);
assert.strictEqual(part.getId(), 'myPart');
// Memento
let memento = part.getMemento(StorageScope.GLOBAL) as any;
assert(memento);
memento.foo = 'bar';
memento.bar = [1, 2, 3];
part.saveState();
// Re-Create to assert memento contents
part = new MyPart(b);
memento = part.getMemento(StorageScope.GLOBAL);
assert(memento);
assert.strictEqual(memento.foo, 'bar');
assert.strictEqual(memento.bar.length, 3);
// Empty Memento stores empty object
delete memento.foo;
delete memento.bar;
part.saveState();
part = new MyPart(b);
memento = part.getMemento(StorageScope.GLOBAL);
assert(memento);
assert.strictEqual(Types.isEmptyObject(memento), true);
});
test('Part Layout with Title and Content', function () {
let b = document.createElement('div');
document.getElementById(fixtureId)!.appendChild(b);
hide(b);
let part = new MyPart2();
part.create(b);
assert(document.getElementById('myPart.title'));
assert(document.getElementById('myPart.content'));
});
test('Part Layout with Content only', function () {
let b = document.createElement('div');
document.getElementById(fixtureId)!.appendChild(b);
hide(b);
let part = new MyPart3();
part.create(b);
assert(!document.getElementById('myPart.title'));
assert(document.getElementById('myPart.content'));
});
});

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { FileKind } from 'vs/platform/files/common/files';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
suite('Breadcrumb Model', function () {
const workspaceService = new TestContextService(new Workspace('ffff', [new WorkspaceFolder({ uri: URI.parse('foo:/bar/baz/ws'), name: 'ws', index: 0 })]));
const configService = new class extends TestConfigurationService {
getValue(...args: any[]) {
if (args[0] === 'breadcrumbs.filePath') {
return 'on';
}
if (args[0] === 'breadcrumbs.symbolPath') {
return 'on';
}
return super.getValue(...args);
}
updateValue() {
return Promise.resolve();
}
};
test('only uri, inside workspace', function () {
let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService);
let elements = model.getElements();
assert.equal(elements.length, 3);
let [one, two, three] = elements as FileElement[];
assert.equal(one.kind, FileKind.FOLDER);
assert.equal(two.kind, FileKind.FOLDER);
assert.equal(three.kind, FileKind.FILE);
assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some');
assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/path');
assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/path/file.ts');
});
test('display uri matters for FileElement', function () {
let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/PATH/file.ts'), URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService);
let elements = model.getElements();
assert.equal(elements.length, 3);
let [one, two, three] = elements as FileElement[];
assert.equal(one.kind, FileKind.FOLDER);
assert.equal(two.kind, FileKind.FOLDER);
assert.equal(three.kind, FileKind.FILE);
assert.equal(one.uri.toString(), 'foo:/bar/baz/ws/some');
assert.equal(two.uri.toString(), 'foo:/bar/baz/ws/some/PATH');
assert.equal(three.uri.toString(), 'foo:/bar/baz/ws/some/PATH/file.ts');
});
test('only uri, outside workspace', function () {
let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService);
let elements = model.getElements();
assert.equal(elements.length, 2);
let [one, two] = elements as FileElement[];
assert.equal(one.kind, FileKind.FOLDER);
assert.equal(two.kind, FileKind.FILE);
assert.equal(one.uri.toString(), 'foo:/outside');
assert.equal(two.uri.toString(), 'foo:/outside/file.ts');
});
});

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* 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 { EditorResourceAccessor, SideBySideEditor, IEditorInputWithPreferredResource } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
import { Schemas } from 'vs/base/common/network';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
export class TestEditorInputWithPreferredResource extends TestEditorInput implements IEditorInputWithPreferredResource {
constructor(resource: URI, public preferredResource: URI, typeId: string) {
super(resource, typeId);
}
}
suite('Workbench editor', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
teardown(() => {
accessor.untitledTextEditorService.dispose();
});
test('EditorResourceAccessor', () => {
const service = accessor.untitledTextEditorService;
assert.ok(!EditorResourceAccessor.getCanonicalUri(null!));
assert.ok(!EditorResourceAccessor.getOriginalUri(null!));
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled)!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.file }));
assert.equal(EditorResourceAccessor.getOriginalUri(untitled)!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.file }));
const file = new TestEditorInput(URI.file('/some/path.txt'), 'editorResourceFileTest');
assert.equal(EditorResourceAccessor.getCanonicalUri(file)!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.untitled }));
assert.equal(EditorResourceAccessor.getOriginalUri(file)!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.untitled }));
const diffEditorInput = new DiffEditorInput('name', 'description', untitled, file);
assert.ok(!EditorResourceAccessor.getCanonicalUri(diffEditorInput));
assert.ok(!EditorResourceAccessor.getCanonicalUri(diffEditorInput, { filterByScheme: Schemas.file }));
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getCanonicalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(diffEditorInput));
assert.ok(!EditorResourceAccessor.getOriginalUri(diffEditorInput, { filterByScheme: Schemas.file }));
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { primary: URI, secondary: URI }).primary.toString(), file.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
assert.equal((EditorResourceAccessor.getOriginalUri(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { primary: URI, secondary: URI }).secondary.toString(), untitled.resource.toString());
const resource = URI.file('/some/path.txt');
const preferredResource = URI.file('/some/PATH.txt');
const fileWithPreferredResource = new TestEditorInputWithPreferredResource(URI.file('/some/path.txt'), URI.file('/some/PATH.txt'), 'editorResourceFileTest');
assert.equal(EditorResourceAccessor.getCanonicalUri(fileWithPreferredResource)?.toString(), resource.toString());
assert.equal(EditorResourceAccessor.getOriginalUri(fileWithPreferredResource)?.toString(), preferredResource.toString());
});
});

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* 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 { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { ITextModel } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
suite('Workbench editor model', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
test('TextDiffEditorModel', async () => {
const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: function (resource: URI): Promise<ITextModel> {
if (resource.scheme === 'test') {
let modelContent = 'Hello Test';
let languageSelection = accessor.modeService.create('json');
return Promise.resolve(accessor.modelService.createModel(modelContent, languageSelection, resource));
}
return Promise.resolve(null!);
}
});
let input = instantiationService.createInstance(ResourceEditorInput, URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), 'name', 'description', undefined);
let otherInput = instantiationService.createInstance(ResourceEditorInput, URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), 'name2', 'description', undefined);
let diffInput = new DiffEditorInput('name', 'description', input, otherInput);
let model = await diffInput.resolve() as TextDiffEditorModel;
assert(model);
assert(model instanceof TextDiffEditorModel);
let diffEditorModel = model.textDiffEditorModel!;
assert(diffEditorModel.original);
assert(diffEditorModel.modified);
model = await diffInput.resolve() as TextDiffEditorModel;
assert(model.isResolved());
assert(diffEditorModel !== model.textDiffEditorModel);
diffInput.dispose();
assert(!model.textDiffEditorModel);
dispose.dispose();
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* 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 { EditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
class MyEditorInput extends EditorInput {
readonly resource = undefined;
getTypeId(): string { return ''; }
resolve(): any { return null; }
}
suite('Workbench editor input', () => {
test('EditorInput', () => {
let counter = 0;
let input = new MyEditorInput();
let otherInput = new MyEditorInput();
assert(input.matches(input));
assert(!input.matches(otherInput));
assert(!input.matches(null));
assert(input.getName());
input.onDispose(() => {
assert(true);
counter++;
});
input.dispose();
assert.equal(counter, 1);
});
test('DiffEditorInput', () => {
let counter = 0;
let input = new MyEditorInput();
input.onDispose(() => {
assert(true);
counter++;
});
let otherInput = new MyEditorInput();
otherInput.onDispose(() => {
assert(true);
counter++;
});
let diffInput = new DiffEditorInput('name', 'description', input, otherInput);
assert.equal(diffInput.originalInput, input);
assert.equal(diffInput.modifiedInput, otherInput);
assert(diffInput.matches(diffInput));
assert(!diffInput.matches(otherInput));
assert(!diffInput.matches(null));
diffInput.dispose();
assert.equal(counter, 0);
});
test('DiffEditorInput disposes when input inside disposes', function () {
let counter = 0;
let input = new MyEditorInput();
let otherInput = new MyEditorInput();
let diffInput = new DiffEditorInput('name', 'description', input, otherInput);
diffInput.onDispose(() => {
counter++;
assert(true);
});
input.dispose();
input = new MyEditorInput();
otherInput = new MyEditorInput();
let diffInput2 = new DiffEditorInput('name', 'description', input, otherInput);
diffInput2.onDispose(() => {
counter++;
assert(true);
});
otherInput.dispose();
assert.equal(counter, 2);
});
});

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* 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 { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { EditorModel } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { ITextBufferFactory } from 'vs/editor/common/model';
import { URI } from 'vs/base/common/uri';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
class MyEditorModel extends EditorModel { }
class MyTextEditorModel extends BaseTextEditorModel {
createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) {
return super.createTextEditorModel(value, resource, preferredMode);
}
isReadonly(): boolean {
return false;
}
}
suite('Workbench editor model', () => {
let instantiationService: TestInstantiationService;
let modeService: IModeService;
setup(() => {
instantiationService = new TestInstantiationService();
modeService = instantiationService.stub(IModeService, ModeServiceImpl);
});
test('EditorModel', async () => {
let counter = 0;
let m = new MyEditorModel();
m.onDispose(() => {
assert(true);
counter++;
});
const model = await m.load();
assert(model === m);
assert.equal(model.isDisposed(), false);
assert.strictEqual(m.isResolved(), true);
m.dispose();
assert.equal(counter, 1);
assert.equal(model.isDisposed(), true);
});
test('BaseTextEditorModel', async () => {
let modelService = stubModelService(instantiationService);
let m = new MyTextEditorModel(modelService, modeService);
const model = await m.load() as MyTextEditorModel;
assert(model === m);
model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain');
assert.strictEqual(m.isResolved(), true);
m.dispose();
});
function stubModelService(instantiationService: TestInstantiationService): IModelService {
const dialogService = new TestDialogService();
const notificationService = new TestNotificationService();
const undoRedoService = new UndoRedoService(dialogService, notificationService);
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService)));
instantiationService.stub(IDialogService, dialogService);
instantiationService.stub(INotificationService, notificationService);
instantiationService.stub(IUndoRedoService, undoRedoService);
instantiationService.stub(IThemeService, new TestThemeService());
return instantiationService.createInstance(ModelServiceImpl);
}
});

View File

@@ -0,0 +1,332 @@
/*---------------------------------------------------------------------------------------------
* 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 { EditorPane, EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane';
import { EditorInput, EditorOptions, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as Platform from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { URI } from 'vs/base/common/uri';
import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { dispose } from 'vs/base/common/lifecycle';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { extUri } from 'vs/base/common/resources';
const NullThemeService = new TestThemeService();
let EditorRegistry: IEditorRegistry = Platform.Registry.as(Extensions.Editors);
let EditorInputRegistry: IEditorInputFactoryRegistry = Platform.Registry.as(EditorExtensions.EditorInputFactories);
export class MyEditor extends EditorPane {
constructor(@ITelemetryService telemetryService: ITelemetryService) {
super('MyEditor', NullTelemetryService, NullThemeService, new TestStorageService());
}
getId(): string { return 'myEditor'; }
layout(): void { }
createEditor(): any { }
}
export class MyOtherEditor extends EditorPane {
constructor(@ITelemetryService telemetryService: ITelemetryService) {
super('myOtherEditor', NullTelemetryService, NullThemeService, new TestStorageService());
}
getId(): string { return 'myOtherEditor'; }
layout(): void { }
createEditor(): any { }
}
class MyInputFactory implements IEditorInputFactory {
canSerialize(editorInput: EditorInput): boolean {
return true;
}
serialize(input: EditorInput): string {
return input.toString();
}
deserialize(instantiationService: IInstantiationService, raw: string): EditorInput {
return {} as EditorInput;
}
}
class MyInput extends EditorInput {
readonly resource = undefined;
getPreferredEditorId(ids: string[]) {
return ids[1];
}
getTypeId(): string {
return '';
}
resolve(): any {
return null;
}
}
class MyOtherInput extends EditorInput {
readonly resource = undefined;
getTypeId(): string {
return '';
}
resolve(): any {
return null;
}
}
class MyResourceEditorInput extends ResourceEditorInput { }
suite('Workbench EditorPane', () => {
test('EditorPane API', async () => {
let e = new MyEditor(NullTelemetryService);
let input = new MyOtherInput();
let options = new EditorOptions();
assert(!e.isVisible());
assert(!e.input);
await e.setInput(input, options, Object.create(null), CancellationToken.None);
assert.strictEqual(input, e.input);
const group = new TestEditorGroupView(1);
e.setVisible(true, group);
assert(e.isVisible());
assert.equal(e.group, group);
input.onDispose(() => {
assert(false);
});
e.dispose();
e.clearInput();
e.setVisible(false, group);
assert(!e.isVisible());
assert(!e.input);
assert(!e.getControl());
});
test('EditorDescriptor', () => {
let d = EditorDescriptor.create(MyEditor, 'id', 'name');
assert.strictEqual(d.getId(), 'id');
assert.strictEqual(d.getName(), 'name');
});
test('Editor Registration', function () {
let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name');
let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name');
let oldEditorsCnt = EditorRegistry.getEditors().length;
let oldInputCnt = (<any>EditorRegistry).getEditorInputs().length;
const dispose1 = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]);
const dispose2 = EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]);
assert.equal(EditorRegistry.getEditors().length, oldEditorsCnt + 2);
assert.equal((<any>EditorRegistry).getEditorInputs().length, oldInputCnt + 3);
assert.strictEqual(EditorRegistry.getEditor(new MyInput()), d2);
assert.strictEqual(EditorRegistry.getEditor(new MyOtherInput()), d2);
assert.strictEqual(EditorRegistry.getEditorById('id1'), d1);
assert.strictEqual(EditorRegistry.getEditorById('id2'), d2);
assert(!EditorRegistry.getEditorById('id3'));
dispose([dispose1, dispose2]);
});
test('Editor Lookup favors specific class over superclass (match on specific class)', function () {
let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name');
const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceEditorInput)]);
let inst = workbenchInstantiationService();
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst);
assert.strictEqual(editor.getId(), 'myEditor');
const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst);
assert.strictEqual(otherEditor.getId(), 'workbench.editors.textResourceEditor');
disposable.dispose();
});
test('Editor Lookup favors specific class over superclass (match on super class)', function () {
let inst = workbenchInstantiationService();
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, URI.file('/fake'), 'fake', '', undefined))!.instantiate(inst);
assert.strictEqual('workbench.editors.textResourceEditor', editor.getId());
});
test('Editor Input Factory', function () {
workbenchInstantiationService().invokeFunction(accessor => EditorInputRegistry.start(accessor));
const disposable = EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory);
let factory = EditorInputRegistry.getEditorInputFactory('myInputId');
assert(factory);
disposable.dispose();
});
test('EditorMemento - basics', function () {
const testGroup0 = new TestEditorGroupView(0);
const testGroup1 = new TestEditorGroupView(1);
const testGroup4 = new TestEditorGroupView(4);
const editorGroupService = new TestEditorGroupsService([
testGroup0,
testGroup1,
new TestEditorGroupView(2)
]);
interface TestViewState {
line: number;
}
const rawMemento = Object.create(null);
let memento = new EditorMemento<TestViewState>('id', 'key', rawMemento, 3, editorGroupService);
let res = memento.loadEditorState(testGroup0, URI.file('/A'));
assert.ok(!res);
memento.saveEditorState(testGroup0, URI.file('/A'), { line: 3 });
res = memento.loadEditorState(testGroup0, URI.file('/A'));
assert.ok(res);
assert.equal(res!.line, 3);
memento.saveEditorState(testGroup1, URI.file('/A'), { line: 5 });
res = memento.loadEditorState(testGroup1, URI.file('/A'));
assert.ok(res);
assert.equal(res!.line, 5);
// Ensure capped at 3 elements
memento.saveEditorState(testGroup0, URI.file('/B'), { line: 1 });
memento.saveEditorState(testGroup0, URI.file('/C'), { line: 1 });
memento.saveEditorState(testGroup0, URI.file('/D'), { line: 1 });
memento.saveEditorState(testGroup0, URI.file('/E'), { line: 1 });
assert.ok(!memento.loadEditorState(testGroup0, URI.file('/A')));
assert.ok(!memento.loadEditorState(testGroup0, URI.file('/B')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/C')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/D')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/E')));
// Save at an unknown group
memento.saveEditorState(testGroup4, URI.file('/E'), { line: 1 });
assert.ok(memento.loadEditorState(testGroup4, URI.file('/E'))); // only gets removed when memento is saved
memento.saveEditorState(testGroup4, URI.file('/C'), { line: 1 });
assert.ok(memento.loadEditorState(testGroup4, URI.file('/C'))); // only gets removed when memento is saved
memento.saveState();
memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService);
assert.ok(memento.loadEditorState(testGroup0, URI.file('/C')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/D')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/E')));
// Check on entries no longer there from invalid groups
assert.ok(!memento.loadEditorState(testGroup4, URI.file('/E')));
assert.ok(!memento.loadEditorState(testGroup4, URI.file('/C')));
memento.clearEditorState(URI.file('/C'), testGroup4);
memento.clearEditorState(URI.file('/E'));
assert.ok(!memento.loadEditorState(testGroup4, URI.file('/C')));
assert.ok(memento.loadEditorState(testGroup0, URI.file('/D')));
assert.ok(!memento.loadEditorState(testGroup0, URI.file('/E')));
// Use fallbackToOtherGroupState
assert.ok(memento.loadEditorState(testGroup4, URI.file('/C'), true));
});
test('EditorMemento - move', function () {
const testGroup0 = new TestEditorGroupView(0);
const editorGroupService = new TestEditorGroupsService([testGroup0]);
interface TestViewState { line: number; }
const rawMemento = Object.create(null);
let memento = new EditorMemento<TestViewState>('id', 'key', rawMemento, 3, editorGroupService);
memento.saveEditorState(testGroup0, URI.file('/some/folder/file-1.txt'), { line: 1 });
memento.saveEditorState(testGroup0, URI.file('/some/folder/file-2.txt'), { line: 2 });
memento.saveEditorState(testGroup0, URI.file('/some/other/file.txt'), { line: 3 });
memento.moveEditorState(URI.file('/some/folder/file-1.txt'), URI.file('/some/folder/file-moved.txt'), extUri);
let res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-1.txt'));
assert.ok(!res);
res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-moved.txt'));
assert.equal(res?.line, 1);
memento.moveEditorState(URI.file('/some/folder'), URI.file('/some/folder-moved'), extUri);
res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-moved.txt'));
assert.equal(res?.line, 1);
res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-2.txt'));
assert.equal(res?.line, 2);
});
test('EditoMemento - use with editor input', function () {
const testGroup0 = new TestEditorGroupView(0);
interface TestViewState {
line: number;
}
class TestEditorInput extends EditorInput {
constructor(public resource: URI, private id = 'testEditorInputForMementoTest') {
super();
}
getTypeId() { return 'testEditorInputForMementoTest'; }
resolve(): Promise<IEditorModel> { return Promise.resolve(null!); }
matches(other: TestEditorInput): boolean {
return other && this.id === other.id && other instanceof TestEditorInput;
}
}
const rawMemento = Object.create(null);
let memento = new EditorMemento<TestViewState>('id', 'key', rawMemento, 3, new TestEditorGroupsService());
const testInputA = new TestEditorInput(URI.file('/A'));
let res = memento.loadEditorState(testGroup0, testInputA);
assert.ok(!res);
memento.saveEditorState(testGroup0, testInputA, { line: 3 });
res = memento.loadEditorState(testGroup0, testInputA);
assert.ok(res);
assert.equal(res!.line, 3);
// State removed when input gets disposed
testInputA.dispose();
res = memento.loadEditorState(testGroup0, testInputA);
assert.ok(!res);
});
return {
MyEditor: MyEditor,
MyOtherEditor: MyOtherEditor
};
});

View File

@@ -0,0 +1,165 @@
/*---------------------------------------------------------------------------------------------
* 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 { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
import { TextModel } from 'vs/editor/common/model/textModel';
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { Range, IRange } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
suite('Editor - Range decorations', () => {
let instantiationService: TestInstantiationService;
let codeEditor: ICodeEditor;
let model: TextModel;
let text: string;
let testObject: RangeHighlightDecorations;
let modelsToDispose: TextModel[] = [];
setup(() => {
instantiationService = <TestInstantiationService>workbenchInstantiationService();
instantiationService.stub(IEditorService, new TestEditorService());
instantiationService.stub(IModeService, ModeServiceImpl);
instantiationService.stub(IModelService, stubModelService(instantiationService));
text = 'LINE1' + '\n' + 'LINE2' + '\n' + 'LINE3' + '\n' + 'LINE4' + '\r\n' + 'LINE5';
model = aModel(URI.file('some_file'));
codeEditor = createTestCodeEditor({ model: model });
instantiationService.stub(IEditorService, 'activeEditor', { get resource() { return codeEditor.getModel()!.uri; } });
instantiationService.stub(IEditorService, 'activeTextEditorControl', codeEditor);
testObject = instantiationService.createInstance(RangeHighlightDecorations);
});
teardown(() => {
codeEditor.dispose();
modelsToDispose.forEach(model => model.dispose());
});
test('highlight range for the resource if it is an active editor', function () {
let range: IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 };
testObject.highlightRange({ resource: model.uri, range });
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([range], actuals);
});
test('remove highlight range', function () {
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
testObject.removeHighlightRange();
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([], actuals);
});
test('highlight range for the resource removes previous highlight', function () {
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
let range: IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 };
testObject.highlightRange({ resource: model.uri, range });
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([range], actuals);
});
test('highlight range for a new resource removes highlight of previous resource', function () {
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
let anotherModel = prepareActiveEditor('anotherModel');
let range: IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 };
testObject.highlightRange({ resource: anotherModel.uri, range });
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([], actuals);
actuals = rangeHighlightDecorations(anotherModel);
assert.deepEqual([range], actuals);
});
test('highlight is removed on model change', function () {
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
prepareActiveEditor('anotherModel');
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([], actuals);
});
test('highlight is removed on cursor position change', function () {
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
codeEditor.trigger('mouse', CoreNavigationCommands.MoveTo.id, {
position: new Position(2, 1)
});
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([], actuals);
});
test('range is not highlight if not active editor', function () {
let model = aModel(URI.file('some model'));
testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } });
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([], actuals);
});
test('previous highlight is not removed if not active editor', function () {
let range: IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 };
testObject.highlightRange({ resource: model.uri, range });
let model1 = aModel(URI.file('some model'));
testObject.highlightRange({ resource: model1.uri, range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 } });
let actuals = rangeHighlightDecorations(model);
assert.deepEqual([range], actuals);
});
function prepareActiveEditor(resource: string): TextModel {
let model = aModel(URI.file(resource));
codeEditor.setModel(model);
return model;
}
function aModel(resource: URI, content: string = text): TextModel {
let model = createTextModel(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource);
modelsToDispose.push(model);
return model;
}
function rangeHighlightDecorations(m: TextModel): IRange[] {
let rangeHighlights: IRange[] = [];
for (let dec of m.getAllDecorations()) {
if (dec.options.className === 'rangeHighlight') {
rangeHighlights.push(dec.range);
}
}
rangeHighlights.sort(Range.compareRangesUsingStarts);
return rangeHighlights;
}
function stubModelService(instantiationService: TestInstantiationService): IModelService {
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(IThemeService, new TestThemeService());
return instantiationService.createInstance(ModelServiceImpl);
}
});

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
suite('Resource text editors', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
test('basics', async () => {
const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' });
accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource);
const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, resource, 'The Name', 'The Description', undefined);
const model = await input.resolve();
assert.ok(model);
assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'function test() {}');
});
test('custom mode', async () => {
ModesRegistry.registerLanguage({
id: 'resource-input-test',
});
const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' });
accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource);
const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, resource, 'The Name', 'The Description', 'resource-input-test');
const model = await input.resolve();
assert.ok(model);
assert.equal(model.textEditorModel.getModeId(), 'resource-input-test');
input.setMode('text');
assert.equal(model.textEditorModel.getModeId(), PLAINTEXT_MODE_ID);
});
});

View File

@@ -0,0 +1,314 @@
/*---------------------------------------------------------------------------------------------
* 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 { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess';
import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { DisposableStore, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { timeout } from 'vs/base/common/async';
import { PickerQuickAccessProvider, FastAndSlowPicks } from 'vs/platform/quickinput/browser/pickerQuickAccess';
suite('QuickAccess', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
let providerDefaultCalled = false;
let providerDefaultCanceled = false;
let providerDefaultDisposed = false;
let provider1Called = false;
let provider1Canceled = false;
let provider1Disposed = false;
let provider2Called = false;
let provider2Canceled = false;
let provider2Disposed = false;
let provider3Called = false;
let provider3Canceled = false;
let provider3Disposed = false;
class TestProviderDefault implements IQuickAccessProvider {
constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { }
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
assert.ok(picker);
providerDefaultCalled = true;
token.onCancellationRequested(() => providerDefaultCanceled = true);
// bring up provider #3
setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix));
return toDisposable(() => providerDefaultDisposed = true);
}
}
class TestProvider1 implements IQuickAccessProvider {
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
assert.ok(picker);
provider1Called = true;
token.onCancellationRequested(() => provider1Canceled = true);
return toDisposable(() => provider1Disposed = true);
}
}
class TestProvider2 implements IQuickAccessProvider {
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
assert.ok(picker);
provider2Called = true;
token.onCancellationRequested(() => provider2Canceled = true);
return toDisposable(() => provider2Disposed = true);
}
}
class TestProvider3 implements IQuickAccessProvider {
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
assert.ok(picker);
provider3Called = true;
token.onCancellationRequested(() => provider3Canceled = true);
// hide without picking
setTimeout(() => picker.hide());
return toDisposable(() => provider3Disposed = true);
}
}
const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] };
const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] };
const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] };
const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] };
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
test('registry', () => {
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
const restore = (registry as QuickAccessRegistry).clear();
assert.ok(!registry.getQuickAccessProvider('test'));
const disposables = new DisposableStore();
disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));
assert(registry.getQuickAccessProvider('') === providerDescriptorDefault);
assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault);
const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));
assert(registry.getQuickAccessProvider('test') === providerDescriptor1);
const providers = registry.getQuickAccessProviders();
assert(providers.some(provider => provider.prefix === 'test'));
disposable.dispose();
assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault);
disposables.dispose();
assert.ok(!registry.getQuickAccessProvider('test'));
restore();
});
test('provider', async () => {
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
const restore = (registry as QuickAccessRegistry).clear();
const disposables = new DisposableStore();
disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));
disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));
disposables.add(registry.registerQuickAccessProvider(providerDescriptor2));
disposables.add(registry.registerQuickAccessProvider(providerDescriptor3));
accessor.quickInputService.quickAccess.show('test');
assert.equal(providerDefaultCalled, false);
assert.equal(provider1Called, true);
assert.equal(provider2Called, false);
assert.equal(provider3Called, false);
assert.equal(providerDefaultCanceled, false);
assert.equal(provider1Canceled, false);
assert.equal(provider2Canceled, false);
assert.equal(provider3Canceled, false);
assert.equal(providerDefaultDisposed, false);
assert.equal(provider1Disposed, false);
assert.equal(provider2Disposed, false);
assert.equal(provider3Disposed, false);
provider1Called = false;
accessor.quickInputService.quickAccess.show('test something');
assert.equal(providerDefaultCalled, false);
assert.equal(provider1Called, false);
assert.equal(provider2Called, true);
assert.equal(provider3Called, false);
assert.equal(providerDefaultCanceled, false);
assert.equal(provider1Canceled, true);
assert.equal(provider2Canceled, false);
assert.equal(provider3Canceled, false);
assert.equal(providerDefaultDisposed, false);
assert.equal(provider1Disposed, true);
assert.equal(provider2Disposed, false);
assert.equal(provider3Disposed, false);
provider2Called = false;
provider1Canceled = false;
provider1Disposed = false;
accessor.quickInputService.quickAccess.show('usedefault');
assert.equal(providerDefaultCalled, true);
assert.equal(provider1Called, false);
assert.equal(provider2Called, false);
assert.equal(provider3Called, false);
assert.equal(providerDefaultCanceled, false);
assert.equal(provider1Canceled, false);
assert.equal(provider2Canceled, true);
assert.equal(provider3Canceled, false);
assert.equal(providerDefaultDisposed, false);
assert.equal(provider1Disposed, false);
assert.equal(provider2Disposed, true);
assert.equal(provider3Disposed, false);
await timeout(1);
assert.equal(providerDefaultCanceled, true);
assert.equal(providerDefaultDisposed, true);
assert.equal(provider3Called, true);
await timeout(1);
assert.equal(provider3Canceled, true);
assert.equal(provider3Disposed, true);
disposables.dispose();
restore();
});
let fastProviderCalled = false;
let slowProviderCalled = false;
let fastAndSlowProviderCalled = false;
let slowProviderCanceled = false;
let fastAndSlowProviderCanceled = false;
class FastTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
constructor() {
super('fast');
}
protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<IQuickPickItem> {
fastProviderCalled = true;
return [{ label: 'Fast Pick' }];
}
}
class SlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
constructor() {
super('slow');
}
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IQuickPickItem>> {
slowProviderCalled = true;
await timeout(1);
if (token.isCancellationRequested) {
slowProviderCanceled = true;
}
return [{ label: 'Slow Pick' }];
}
}
class FastAndSlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
constructor() {
super('bothFastAndSlow');
}
protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks<IQuickPickItem> {
fastAndSlowProviderCalled = true;
return {
picks: [{ label: 'Fast Pick' }],
additionalPicks: (async () => {
await timeout(1);
if (token.isCancellationRequested) {
fastAndSlowProviderCanceled = true;
}
return [{ label: 'Slow Pick' }];
})()
};
}
}
const fastProviderDescriptor = { ctor: FastTestQuickPickProvider, prefix: 'fast', helpEntries: [] };
const slowProviderDescriptor = { ctor: SlowTestQuickPickProvider, prefix: 'slow', helpEntries: [] };
const fastAndSlowProviderDescriptor = { ctor: FastAndSlowTestQuickPickProvider, prefix: 'bothFastAndSlow', helpEntries: [] };
test('quick pick access', async () => {
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
const restore = (registry as QuickAccessRegistry).clear();
const disposables = new DisposableStore();
disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor));
disposables.add(registry.registerQuickAccessProvider(slowProviderDescriptor));
disposables.add(registry.registerQuickAccessProvider(fastAndSlowProviderDescriptor));
accessor.quickInputService.quickAccess.show('fast');
assert.equal(fastProviderCalled, true);
assert.equal(slowProviderCalled, false);
assert.equal(fastAndSlowProviderCalled, false);
fastProviderCalled = false;
accessor.quickInputService.quickAccess.show('slow');
await timeout(2);
assert.equal(fastProviderCalled, false);
assert.equal(slowProviderCalled, true);
assert.equal(slowProviderCanceled, false);
assert.equal(fastAndSlowProviderCalled, false);
slowProviderCalled = false;
accessor.quickInputService.quickAccess.show('bothFastAndSlow');
await timeout(2);
assert.equal(fastProviderCalled, false);
assert.equal(slowProviderCalled, false);
assert.equal(fastAndSlowProviderCalled, true);
assert.equal(fastAndSlowProviderCanceled, false);
fastAndSlowProviderCalled = false;
accessor.quickInputService.quickAccess.show('slow');
accessor.quickInputService.quickAccess.show('bothFastAndSlow');
accessor.quickInputService.quickAccess.show('fast');
assert.equal(fastProviderCalled, true);
assert.equal(slowProviderCalled, true);
assert.equal(fastAndSlowProviderCalled, true);
await timeout(2);
assert.equal(slowProviderCanceled, true);
assert.equal(fastAndSlowProviderCanceled, true);
disposables.dispose();
restore();
});
});

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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 Platform from 'vs/platform/registry/common/platform';
import { ViewletDescriptor, Extensions, Viewlet, ViewletRegistry } from 'vs/workbench/browser/viewlet';
import * as Types from 'vs/base/common/types';
suite('Viewlets', () => {
class TestViewlet extends Viewlet {
constructor() {
super('id', null!, null!, null!, null!, null!, null!, null!, null!, null!, null!);
}
layout(dimension: any): void {
throw new Error('Method not implemented.');
}
}
test('ViewletDescriptor API', function () {
let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
assert.strictEqual(d.cssClass, 'class');
assert.strictEqual(d.order, 5);
});
test('Editor Aware ViewletDescriptor API', function () {
let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
});
test('Viewlet extension point and registration', function () {
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet));
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet));
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets));
let oldCount = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length;
let d = ViewletDescriptor.create(TestViewlet, 'reg-test-id', 'name');
Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet(d);
assert(d === Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet('reg-test-id'));
assert.equal(oldCount + 1, Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* 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 { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto';
import { VSBuffer } from 'vs/base/common/buffer';
suite('SemanticTokensDto', () => {
function toArr(arr: Uint32Array): number[] {
const result: number[] = [];
for (let i = 0, len = arr.length; i < len; i++) {
result[i] = arr[i];
}
return result;
}
function assertEqualFull(actual: IFullSemanticTokensDto, expected: IFullSemanticTokensDto): void {
const convert = (dto: IFullSemanticTokensDto) => {
return {
id: dto.id,
type: dto.type,
data: toArr(dto.data)
};
};
assert.deepEqual(convert(actual), convert(expected));
}
function assertEqualDelta(actual: IDeltaSemanticTokensDto, expected: IDeltaSemanticTokensDto): void {
const convertOne = (delta: { start: number; deleteCount: number; data?: Uint32Array; }) => {
if (!delta.data) {
return delta;
}
return {
start: delta.start,
deleteCount: delta.deleteCount,
data: toArr(delta.data)
};
};
const convert = (dto: IDeltaSemanticTokensDto) => {
return {
id: dto.id,
type: dto.type,
deltas: dto.deltas.map(convertOne)
};
};
assert.deepEqual(convert(actual), convert(expected));
}
function testRoundTrip(value: ISemanticTokensDto): void {
const decoded = decodeSemanticTokensDto(encodeSemanticTokensDto(value));
if (value.type === 'full' && decoded.type === 'full') {
assertEqualFull(decoded, value);
} else if (value.type === 'delta' && decoded.type === 'delta') {
assertEqualDelta(decoded, value);
} else {
assert.fail('wrong type');
}
}
test('full encoding', () => {
testRoundTrip({
id: 12,
type: 'full',
data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4])
});
});
test('delta encoding', () => {
testRoundTrip({
id: 12,
type: 'delta',
deltas: [{
start: 0,
deleteCount: 4,
data: undefined
}, {
start: 15,
deleteCount: 0,
data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4])
}, {
start: 27,
deleteCount: 5,
data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4, 1, 2, 3, 4, 5, 6, 7, 8, 9])
}]
});
});
test('partial array buffer', () => {
const sharedArr = new Uint32Array([
(1 << 24) + (2 << 16) + (3 << 8) + 4,
1, 2, 3, 4, 5, (1 << 24) + (2 << 16) + (3 << 8) + 4
]);
testRoundTrip({
id: 12,
type: 'delta',
deltas: [{
start: 0,
deleteCount: 4,
data: sharedArr.subarray(0, 1)
}, {
start: 15,
deleteCount: 0,
data: sharedArr.subarray(1, sharedArr.length)
}]
});
});
test('issue #94521: unusual backing array buffer', () => {
function wrapAndSliceUint8Arry(buff: Uint8Array, prefixLength: number, suffixLength: number): Uint8Array {
const wrapped = new Uint8Array(prefixLength + buff.byteLength + suffixLength);
wrapped.set(buff, prefixLength);
return wrapped.subarray(prefixLength, prefixLength + buff.byteLength);
}
function wrapAndSlice(buff: VSBuffer, prefixLength: number, suffixLength: number): VSBuffer {
return VSBuffer.wrap(wrapAndSliceUint8Arry(buff.buffer, prefixLength, suffixLength));
}
const dto: ISemanticTokensDto = {
id: 5,
type: 'full',
data: new Uint32Array([1, 2, 3, 4, 5])
};
const encoded = encodeSemanticTokensDto(dto);
// with misaligned prefix and misaligned suffix
assertEqualFull(<IFullSemanticTokensDto>decodeSemanticTokensDto(wrapAndSlice(encoded, 1, 1)), dto);
// with misaligned prefix and aligned suffix
assertEqualFull(<IFullSemanticTokensDto>decodeSemanticTokensDto(wrapAndSlice(encoded, 1, 4)), dto);
// with aligned prefix and misaligned suffix
assertEqualFull(<IFullSemanticTokensDto>decodeSemanticTokensDto(wrapAndSlice(encoded, 4, 1)), dto);
// with aligned prefix and aligned suffix
assertEqualFull(<IFullSemanticTokensDto>decodeSemanticTokensDto(wrapAndSlice(encoded, 4, 4)), dto);
});
});

View File

@@ -0,0 +1,180 @@
/*---------------------------------------------------------------------------------------------
* 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 { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
suite('Memento', () => {
let context: StorageScope | undefined = undefined;
let storage: IStorageService;
setup(() => {
storage = new TestStorageService();
});
test('Loading and Saving Memento with Scopes', () => {
let myMemento = new Memento('memento.test', storage);
// Global
let memento = myMemento.getMemento(StorageScope.GLOBAL);
memento.foo = [1, 2, 3];
let globalMemento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert(memento);
memento.foo = 'Hello World';
myMemento.saveMemento();
// Global
memento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(memento, { foo: [1, 2, 3] });
globalMemento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, { foo: 'Hello World' });
// Assert the Mementos are stored properly in storage
assert.deepEqual(JSON.parse(storage.get('memento/memento.test', StorageScope.GLOBAL)!), { foo: [1, 2, 3] });
assert.deepEqual(JSON.parse(storage.get('memento/memento.test', StorageScope.WORKSPACE)!), { foo: 'Hello World' });
// Delete Global
memento = myMemento.getMemento(context!);
delete memento.foo;
// Delete Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
delete memento.foo;
myMemento.saveMemento();
// Global
memento = myMemento.getMemento(context!);
assert.deepEqual(memento, {});
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, {});
// Assert the Mementos are also removed from storage
assert.strictEqual(storage.get('memento/memento.test', StorageScope.GLOBAL, null!), null);
assert.strictEqual(storage.get('memento/memento.test', StorageScope.WORKSPACE, null!), null);
});
test('Save and Load', () => {
let myMemento = new Memento('memento.test', storage);
// Global
let memento = myMemento.getMemento(context!);
memento.foo = [1, 2, 3];
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert(memento);
memento.foo = 'Hello World';
myMemento.saveMemento();
// Global
memento = myMemento.getMemento(context!);
assert.deepEqual(memento, { foo: [1, 2, 3] });
let globalMemento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, { foo: 'Hello World' });
// Global
memento = myMemento.getMemento(context!);
memento.foo = [4, 5, 6];
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert(memento);
memento.foo = 'World Hello';
myMemento.saveMemento();
// Global
memento = myMemento.getMemento(context!);
assert.deepEqual(memento, { foo: [4, 5, 6] });
globalMemento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, { foo: 'World Hello' });
// Delete Global
memento = myMemento.getMemento(context!);
delete memento.foo;
// Delete Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
delete memento.foo;
myMemento.saveMemento();
// Global
memento = myMemento.getMemento(context!);
assert.deepEqual(memento, {});
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, {});
});
test('Save and Load - 2 Components with same id', () => {
let myMemento = new Memento('memento.test', storage);
let myMemento2 = new Memento('memento.test', storage);
// Global
let memento = myMemento.getMemento(context!);
memento.foo = [1, 2, 3];
memento = myMemento2.getMemento(context!);
memento.bar = [1, 2, 3];
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert(memento);
memento.foo = 'Hello World';
memento = myMemento2.getMemento(StorageScope.WORKSPACE);
assert(memento);
memento.bar = 'Hello World';
myMemento.saveMemento();
myMemento2.saveMemento();
// Global
memento = myMemento.getMemento(context!);
assert.deepEqual(memento, { foo: [1, 2, 3], bar: [1, 2, 3] });
let globalMemento = myMemento.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
memento = myMemento2.getMemento(context!);
assert.deepEqual(memento, { foo: [1, 2, 3], bar: [1, 2, 3] });
globalMemento = myMemento2.getMemento(StorageScope.GLOBAL);
assert.deepEqual(globalMemento, memento);
// Workspace
memento = myMemento.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, { foo: 'Hello World', bar: 'Hello World' });
memento = myMemento2.getMemento(StorageScope.WORKSPACE);
assert.deepEqual(memento, { foo: 'Hello World', bar: 'Hello World' });
});
});

View File

@@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* 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 { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications';
import { Action } from 'vs/base/common/actions';
import { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification';
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
suite('Notifications', () => {
test('Items', () => {
// Invalid
assert.ok(!NotificationViewItem.create({ severity: Severity.Error, message: '' }));
assert.ok(!NotificationViewItem.create({ severity: Severity.Error, message: null! }));
// Duplicates
let item1 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' })!;
let item2 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' })!;
let item3 = NotificationViewItem.create({ severity: Severity.Info, message: 'Info Message' })!;
let item4 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', source: 'Source' })!;
let item5 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } })!;
let item6 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] }, progress: { infinite: true } })!;
assert.equal(item1.equals(item1), true);
assert.equal(item2.equals(item2), true);
assert.equal(item3.equals(item3), true);
assert.equal(item4.equals(item4), true);
assert.equal(item5.equals(item5), true);
assert.equal(item1.equals(item2), true);
assert.equal(item1.equals(item3), false);
assert.equal(item1.equals(item4), false);
assert.equal(item1.equals(item5), false);
// Progress
assert.equal(item1.hasProgress, false);
assert.equal(item6.hasProgress, true);
// Message Box
assert.equal(item5.canCollapse, false);
assert.equal(item5.expanded, true);
// Events
let called = 0;
item1.onDidChangeExpansion(() => {
called++;
});
item1.expand();
item1.expand();
item1.collapse();
item1.collapse();
assert.equal(called, 2);
called = 0;
item1.onDidChangeContent(e => {
if (e.kind === NotificationViewItemContentChangeKind.PROGRESS) {
called++;
}
});
item1.progress.infinite();
item1.progress.done();
assert.equal(called, 2);
called = 0;
item1.onDidChangeContent(e => {
if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) {
called++;
}
});
item1.updateMessage('message update');
called = 0;
item1.onDidChangeContent(e => {
if (e.kind === NotificationViewItemContentChangeKind.SEVERITY) {
called++;
}
});
item1.updateSeverity(Severity.Error);
called = 0;
item1.onDidChangeContent(e => {
if (e.kind === NotificationViewItemContentChangeKind.ACTIONS) {
called++;
}
});
item1.updateActions({ primary: [new Action('id2', 'label')] });
assert.equal(called, 1);
called = 0;
item1.onDidChangeVisibility(e => {
called++;
});
item1.updateVisibility(true);
item1.updateVisibility(false);
item1.updateVisibility(false);
assert.equal(called, 2);
called = 0;
item1.onDidClose(() => {
called++;
});
item1.close();
assert.equal(called, 1);
// Error with Action
let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!;
assert.equal(item7.actions!.primary!.length, 1);
// Filter
let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!;
assert.equal(item8.silent, true);
let item9 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.OFF)!;
assert.equal(item9.silent, false);
let item10 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.ERROR)!;
assert.equal(item10.silent, false);
let item11 = NotificationViewItem.create({ severity: Severity.Warning, message: 'Error Message' }, NotificationsFilter.ERROR)!;
assert.equal(item11.silent, true);
});
test('Model', () => {
const model = new NotificationsModel();
let lastNotificationEvent!: INotificationChangeEvent;
model.onDidChangeNotification(e => {
lastNotificationEvent = e;
});
let lastStatusMessageEvent!: IStatusMessageChangeEvent;
model.onDidChangeStatusMessage(e => {
lastStatusMessageEvent = e;
});
let item1: INotification = { severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } };
let item2: INotification = { severity: Severity.Warning, message: 'Warning Message', source: 'Some Source' };
let item2Duplicate: INotification = { severity: Severity.Warning, message: 'Warning Message', source: 'Some Source' };
let item3: INotification = { severity: Severity.Info, message: 'Info Message' };
let item1Handle = model.addNotification(item1);
assert.equal(lastNotificationEvent.item.severity, item1.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
item1Handle.updateMessage('Error Message');
assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.MESSAGE);
item1Handle.updateSeverity(Severity.Error);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.SEVERITY);
item1Handle.updateActions({ primary: [], secondary: [] });
assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.ACTIONS);
item1Handle.progress.infinite();
assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE);
assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.PROGRESS);
let item2Handle = model.addNotification(item2);
assert.equal(lastNotificationEvent.item.severity, item2.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
model.addNotification(item3);
assert.equal(lastNotificationEvent.item.severity, item3.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
assert.equal(model.notifications.length, 3);
let called = 0;
item1Handle.onDidClose(() => {
called++;
});
item1Handle.close();
assert.equal(called, 1);
assert.equal(model.notifications.length, 2);
assert.equal(lastNotificationEvent.item.severity, item1.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message);
assert.equal(lastNotificationEvent.index, 2);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE);
model.addNotification(item2Duplicate);
assert.equal(model.notifications.length, 2);
assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD);
item2Handle.close();
assert.equal(model.notifications.length, 1);
assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE);
model.notifications[0].expand();
assert.equal(lastNotificationEvent.item.severity, item3.severity);
assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message);
assert.equal(lastNotificationEvent.index, 0);
assert.equal(lastNotificationEvent.kind, NotificationChangeType.EXPAND_COLLAPSE);
const disposable = model.showStatusMessage('Hello World');
assert.equal(model.statusMessage!.message, 'Hello World');
assert.equal(lastStatusMessageEvent.item.message, model.statusMessage!.message);
assert.equal(lastStatusMessageEvent.kind, StatusMessageChangeType.ADD);
disposable.dispose();
assert.ok(!model.statusMessage);
assert.equal(lastStatusMessageEvent.kind, StatusMessageChangeType.REMOVE);
let disposable2 = model.showStatusMessage('Hello World 2');
const disposable3 = model.showStatusMessage('Hello World 3');
assert.equal(model.statusMessage!.message, 'Hello World 3');
disposable2.dispose();
assert.equal(model.statusMessage!.message, 'Hello World 3');
disposable3.dispose();
assert.ok(!model.statusMessage);
});
});

View File

@@ -0,0 +1,219 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { join } from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceContextService, IWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { InMemoryStorageService, IWillSaveStateEvent } from 'vs/platform/storage/common/storage';
import { WorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
export class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
declare readonly _serviceBrand: undefined;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
}
getEOL(resource: URI, language?: string): string {
const eol = this.configurationService.getValue<string>('files.eol', { overrideIdentifier: language, resource });
if (eol && eol !== 'auto') {
return eol;
}
return (isLinux || isMacintosh) ? '\n' : '\r\n';
}
}
export class TestContextService implements IWorkspaceContextService {
declare readonly _serviceBrand: undefined;
private workspace: Workspace;
private options: object;
private readonly _onDidChangeWorkspaceName: Emitter<void>;
get onDidChangeWorkspaceName(): Event<void> { return this._onDidChangeWorkspaceName.event; }
private readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent>;
get onDidChangeWorkspaceFolders(): Event<IWorkspaceFoldersChangeEvent> { return this._onDidChangeWorkspaceFolders.event; }
private readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState>;
get onDidChangeWorkbenchState(): Event<WorkbenchState> { return this._onDidChangeWorkbenchState.event; }
constructor(workspace = TestWorkspace, options = null) {
this.workspace = workspace;
this.options = options || Object.create(null);
this._onDidChangeWorkspaceName = new Emitter<void>();
this._onDidChangeWorkspaceFolders = new Emitter<IWorkspaceFoldersChangeEvent>();
this._onDidChangeWorkbenchState = new Emitter<WorkbenchState>();
}
getFolders(): IWorkspaceFolder[] {
return this.workspace ? this.workspace.folders : [];
}
getWorkbenchState(): WorkbenchState {
if (this.workspace.configuration) {
return WorkbenchState.WORKSPACE;
}
if (this.workspace.folders.length) {
return WorkbenchState.FOLDER;
}
return WorkbenchState.EMPTY;
}
getCompleteWorkspace(): Promise<IWorkspace> {
return Promise.resolve(this.getWorkspace());
}
getWorkspace(): IWorkspace {
return this.workspace;
}
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null {
return this.workspace.getFolder(resource);
}
setWorkspace(workspace: any): void {
this.workspace = workspace;
}
getOptions() {
return this.options;
}
updateOptions() { }
isInsideWorkspace(resource: URI): boolean {
if (resource && this.workspace) {
return resources.isEqualOrParent(resource, this.workspace.folders[0].uri);
}
return false;
}
toResource(workspaceRelativePath: string): URI {
return URI.file(join('C:\\', workspaceRelativePath));
}
isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && resources.isEqual(this.workspace.folders[0].uri, workspaceIdentifier);
}
}
export class TestStorageService extends InMemoryStorageService {
readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
readonly onWillSaveState = this._onWillSaveState.event;
}
export class TestWorkingCopyService extends WorkingCopyService { }
export class TestWorkingCopy extends Disposable implements IWorkingCopy {
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
private readonly _onDidChangeContent = this._register(new Emitter<void>());
readonly onDidChangeContent = this._onDidChangeContent.event;
private readonly _onDispose = this._register(new Emitter<void>());
readonly onDispose = this._onDispose.event;
readonly capabilities = WorkingCopyCapabilities.None;
readonly name = resources.basename(this.resource);
private dirty = false;
constructor(public readonly resource: URI, isDirty = false) {
super();
this.dirty = isDirty;
}
setDirty(dirty: boolean): void {
if (this.dirty !== dirty) {
this.dirty = dirty;
this._onDidChangeDirty.fire();
}
}
setContent(content: string): void {
this._onDidChangeContent.fire();
}
isDirty(): boolean {
return this.dirty;
}
async save(options?: ISaveOptions): Promise<boolean> {
return true;
}
async revert(options?: IRevertOptions): Promise<void> {
this.setDirty(false);
}
async backup(token: CancellationToken): Promise<IWorkingCopyBackup> {
return {};
}
dispose(): void {
this._onDispose.fire();
super.dispose();
}
}
export class TestWorkingCopyFileService implements IWorkingCopyFileService {
declare readonly _serviceBrand: undefined;
onWillRunWorkingCopyFileOperation: Event<WorkingCopyFileEvent> = Event.None;
onDidFailWorkingCopyFileOperation: Event<WorkingCopyFileEvent> = Event.None;
onDidRunWorkingCopyFileOperation: Event<WorkingCopyFileEvent> = Event.None;
addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { return Disposable.None; }
async delete(resources: URI[], options?: { useTrash?: boolean | undefined; recursive?: boolean | undefined; } | undefined): Promise<void> { }
registerWorkingCopyProvider(provider: (resourceOrFolder: URI) => IWorkingCopy[]): IDisposable { return Disposable.None; }
getDirty(resource: URI): IWorkingCopy[] { return []; }
create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean | undefined; } | undefined): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
createFolder(resource: URI): Promise<IFileStatWithMetadata> { throw new Error('Method not implemented.'); }
move(files: { source: URI; target: URI; }[], options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
copy(files: { source: URI; target: URI; }[], options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata[]> { throw new Error('Method not implemented.'); }
}
export function mock<T>(): Ctor<T> {
return function () { } as any;
}
export interface Ctor<T> {
new(): T;
}
export class TestExtensionService extends NullExtensionService { }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ISearchService, IFileQuery } from 'vs/workbench/services/search/common/search';
import { MainThreadWorkspace } from 'vs/workbench/api/browser/mainThreadWorkspace';
import * as assert from 'assert';
import { SingleProxyRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
suite('MainThreadWorkspace', () => {
let configService: TestConfigurationService;
let instantiationService: TestInstantiationService;
setup(() => {
instantiationService = workbenchInstantiationService() as TestInstantiationService;
configService = instantiationService.get(IConfigurationService) as TestConfigurationService;
configService.setUserConfiguration('search', {});
});
test('simple', () => {
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.equal(query.folderQueries.length, 1);
assert.equal(query.folderQueries[0].disregardIgnoreFiles, true);
assert.deepEqual(query.includePattern, { 'foo': true });
assert.equal(query.maxResults, 10);
return Promise.resolve({ results: [] });
}
});
const mtw: MainThreadWorkspace = instantiationService.createInstance(<any>MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }));
return mtw.$startFileSearch('foo', null, null, 10, new CancellationTokenSource().token);
});
test('exclude defaults', () => {
configService.setUserConfiguration('search', {
'exclude': { 'searchExclude': true }
});
configService.setUserConfiguration('files', {
'exclude': { 'filesExclude': true }
});
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.equal(query.folderQueries.length, 1);
assert.equal(query.folderQueries[0].disregardIgnoreFiles, true);
assert.deepEqual(query.folderQueries[0].excludePattern, { 'filesExclude': true });
return Promise.resolve({ results: [] });
}
});
const mtw: MainThreadWorkspace = instantiationService.createInstance(<any>MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }));
return mtw.$startFileSearch('', null, null, 10, new CancellationTokenSource().token);
});
test('disregard excludes', () => {
configService.setUserConfiguration('search', {
'exclude': { 'searchExclude': true }
});
configService.setUserConfiguration('files', {
'exclude': { 'filesExclude': true }
});
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.equal(query.folderQueries[0].excludePattern, undefined);
assert.deepEqual(query.excludePattern, undefined);
return Promise.resolve({ results: [] });
}
});
const mtw: MainThreadWorkspace = instantiationService.createInstance(<any>MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }));
return mtw.$startFileSearch('', null, false, 10, new CancellationTokenSource().token);
});
test('exclude string', () => {
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.equal(query.folderQueries[0].excludePattern, undefined);
assert.deepEqual(query.excludePattern, { 'exclude/**': true });
return Promise.resolve({ results: [] });
}
});
const mtw: MainThreadWorkspace = instantiationService.createInstance(<any>MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }));
return mtw.$startFileSearch('', null, 'exclude/**', 10, new CancellationTokenSource().token);
});
});

View File

@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IColorRegistry, Extensions, ColorContribution } from 'vs/platform/theme/common/colorRegistry';
import { asText } from 'vs/platform/request/common/request';
import * as pfs from 'vs/base/node/pfs';
import * as path from 'vs/base/common/path';
import * as assert from 'assert';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { CancellationToken } from 'vs/base/common/cancellation';
import { RequestService } from 'vs/platform/request/node/requestService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import 'vs/workbench/workbench.desktop.main';
import { NullLogService } from 'vs/platform/log/common/log';
interface ColorInfo {
description: string;
offset: number;
length: number;
}
interface DescriptionDiff {
docDescription: string;
specDescription: string;
}
export const experimental: string[] = []; // 'settings.modifiedItemForeground', 'editorUnnecessary.foreground' ];
suite('Color Registry', function () {
test('all colors documented in theme-color.md', async function () {
const reqContext = await new RequestService(new TestConfigurationService(), new NullLogService()).request({ url: 'https://raw.githubusercontent.com/microsoft/vscode-docs/vnext/api/references/theme-color.md' }, CancellationToken.None);
const content = (await asText(reqContext))!;
const expression = /\-\s*\`([\w\.]+)\`: (.*)/g;
let m: RegExpExecArray | null;
let colorsInDoc: { [id: string]: ColorInfo } = Object.create(null);
let nColorsInDoc = 0;
while (m = expression.exec(content)) {
colorsInDoc[m[1]] = { description: m[2], offset: m.index, length: m.length };
nColorsInDoc++;
}
assert.ok(nColorsInDoc > 0, 'theme-color.md contains to color descriptions');
let missing = Object.create(null);
let descriptionDiffs: { [id: string]: DescriptionDiff } = Object.create(null);
let themingRegistry = Registry.as<IColorRegistry>(Extensions.ColorContribution);
for (let color of themingRegistry.getColors()) {
if (!colorsInDoc[color.id]) {
if (!color.deprecationMessage) {
missing[color.id] = getDescription(color);
}
} else {
let docDescription = colorsInDoc[color.id].description;
let specDescription = getDescription(color);
if (docDescription !== specDescription) {
descriptionDiffs[color.id] = { docDescription, specDescription };
}
delete colorsInDoc[color.id];
}
}
let colorsInExtensions = await getColorsFromExtension();
for (let colorId in colorsInExtensions) {
if (!colorsInDoc[colorId]) {
missing[colorId] = colorsInExtensions[colorId];
} else {
delete colorsInDoc[colorId];
}
}
for (let colorId of experimental) {
if (missing[colorId]) {
delete missing[colorId];
}
if (colorsInDoc[colorId]) {
assert.fail(`Color ${colorId} found in doc but marked experimental. Please remove from experimental list.`);
}
}
let undocumentedKeys = Object.keys(missing).map(k => `\`${k}\`: ${missing[k]}`);
assert.deepEqual(undocumentedKeys, [], 'Undocumented colors ids');
let superfluousKeys = Object.keys(colorsInDoc);
assert.deepEqual(superfluousKeys, [], 'Colors ids in doc that do not exist');
});
});
function getDescription(color: ColorContribution) {
let specDescription = color.description;
if (color.deprecationMessage) {
specDescription = specDescription + ' ' + color.deprecationMessage;
}
return specDescription;
}
async function getColorsFromExtension(): Promise<{ [id: string]: string }> {
let extPath = getPathFromAmdModule(require, '../../../../../extensions');
let extFolders = await pfs.readDirsInDir(extPath);
let result: { [id: string]: string } = Object.create(null);
for (let folder of extFolders) {
try {
let packageJSON = JSON.parse((await pfs.readFile(path.join(extPath, folder, 'package.json'))).toString());
let contributes = packageJSON['contributes'];
if (contributes) {
let colors = contributes['colors'];
if (colors) {
for (let color of colors) {
let colorId = color['id'];
if (colorId) {
result[colorId] = colorId['description'];
}
}
}
}
} catch (e) {
// ignore
}
}
return result;
}

View File

@@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/workbench/contrib/search/browser/search.contribution'; // load contributions
import * as assert from 'assert';
import * as fs from 'fs';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ISearchService } from 'vs/workbench/services/search/common/search';
import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as minimist from 'minimist';
import * as path from 'vs/base/common/path';
import { LocalSearchService } from 'vs/workbench/services/search/electron-browser/searchService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { TestEditorService, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { IModelService } from 'vs/editor/common/services/modelService';
import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel';
import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { Event, Emitter } from 'vs/base/common/event';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
// declare var __dirname: string;
// Checkout sources to run against:
// git clone --separate-git-dir=testGit --no-checkout --single-branch https://chromium.googlesource.com/chromium/src testWorkspace
// cd testWorkspace; git checkout 39a7f93d67f7
// Run from repository root folder with (test.bat on Windows): ./scripts/test-int-mocha.sh --grep TextSearch.performance --timeout 500000 --testWorkspace <path>
suite.skip('TextSearch performance (integration)', () => {
test('Measure', () => {
if (process.env['VSCODE_PID']) {
return undefined; // TODO@Rob find out why test fails when run from within VS Code
}
const n = 3;
const argv = minimist(process.argv);
const testWorkspaceArg = argv['testWorkspace'];
const testWorkspacePath = testWorkspaceArg ? path.resolve(testWorkspaceArg) : __dirname;
if (!fs.existsSync(testWorkspacePath)) {
throw new Error(`--testWorkspace doesn't exist`);
}
const telemetryService = new TestTelemetryService();
const configurationService = new TestConfigurationService();
const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService);
const logService = new NullLogService();
const dialogService = new TestDialogService();
const notificationService = new TestNotificationService();
const undoRedoService = new UndoRedoService(dialogService, notificationService);
const instantiationService = new InstantiationService(new ServiceCollection(
[ITelemetryService, telemetryService],
[IConfigurationService, configurationService],
[ITextResourcePropertiesService, textResourcePropertiesService],
[IDialogService, dialogService],
[INotificationService, notificationService],
[IUndoRedoService, undoRedoService],
[IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService)],
[IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))],
[IEditorService, new TestEditorService()],
[IEditorGroupsService, new TestEditorGroupsService()],
[IEnvironmentService, TestEnvironmentService],
[IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)],
[ISearchService, createSyncDescriptor(LocalSearchService)],
[ILogService, logService]
));
const queryOptions: ITextQueryBuilderOptions = {
maxResults: 2048
};
const searchModel: SearchModel = instantiationService.createInstance(SearchModel);
function runSearch(): Promise<any> {
const queryBuilder: QueryBuilder = instantiationService.createInstance(QueryBuilder);
const query = queryBuilder.text({ pattern: 'static_library(' }, [URI.file(testWorkspacePath)], queryOptions);
// Wait for the 'searchResultsFinished' event, which is fired after the search() promise is resolved
const onSearchResultsFinished = Event.filter(telemetryService.eventLogged, e => e.name === 'searchResultsFinished');
Event.once(onSearchResultsFinished)(onComplete);
function onComplete(): void {
try {
const allEvents = telemetryService.events.map(e => JSON.stringify(e)).join('\n');
assert.equal(telemetryService.events.length, 3, 'Expected 3 telemetry events, got:\n' + allEvents);
const [firstRenderEvent, resultsShownEvent, resultsFinishedEvent] = telemetryService.events;
assert.equal(firstRenderEvent.name, 'searchResultsFirstRender');
assert.equal(resultsShownEvent.name, 'searchResultsShown');
assert.equal(resultsFinishedEvent.name, 'searchResultsFinished');
telemetryService.events = [];
resolve!(resultsFinishedEvent);
} catch (e) {
// Fail the runSearch() promise
error!(e);
}
}
let resolve: (result: any) => void;
let error: (error: Error) => void;
return new Promise((_resolve, _error) => {
resolve = _resolve;
error = _error;
// Don't wait on this promise, we're waiting on the event fired above
searchModel.search(query).then(
null,
_error);
});
}
const finishedEvents: any[] = [];
return runSearch() // Warm-up first
.then(() => {
if (testWorkspaceArg) { // Don't measure by default
let i = n;
return (function iterate(): Promise<undefined> | undefined {
if (!i--) {
return;
}
return runSearch()
.then((resultsFinishedEvent: any) => {
console.log(`Iteration ${n - i}: ${resultsFinishedEvent.data.duration / 1000}s`);
finishedEvents.push(resultsFinishedEvent);
return iterate();
});
})()!.then(() => {
const totalTime = finishedEvents.reduce((sum, e) => sum + e.data.duration, 0);
console.log(`Avg duration: ${totalTime / n / 1000}s`);
});
}
return undefined;
});
});
});
class TestTelemetryService implements ITelemetryService {
public _serviceBrand: undefined;
public isOptedIn = true;
public sendErrorTelemetry = true;
public events: any[] = [];
private readonly emitter = new Emitter<any>();
public get eventLogged(): Event<any> {
return this.emitter.event;
}
public setEnabled(value: boolean): void {
}
public setExperimentProperty(name: string, value: string): void {
}
public publicLog(eventName: string, data?: any): Promise<void> {
const event = { name: eventName, data: data };
this.events.push(event);
this.emitter.fire(event);
return Promise.resolve();
}
public publicLog2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
return this.publicLog(eventName, data as any);
}
public publicLogError(eventName: string, data?: any): Promise<void> {
return this.publicLog(eventName, data);
}
public publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
return this.publicLogError(eventName, data as any);
}
public getTelemetryInfo(): Promise<ITelemetryInfo> {
return Promise.resolve({
instanceId: 'someValue.instanceId',
sessionId: 'someValue.sessionId',
machineId: 'someValue.machineId'
});
}
}

View File

@@ -0,0 +1,280 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService, TestPathService, TestEncodingOracle, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices';
import { Event } from 'vs/base/common/event';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { FileOperationError, IFileService } from 'vs/platform/files/common/files';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModelService } from 'vs/editor/common/services/modelService';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IDialogService, IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { URI } from 'vs/base/common/uri';
import { IReadTextFileOptions, ITextFileStreamContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
import { IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { LogLevel, ILogService } from 'vs/platform/log/common/log';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOSProperties, IOSStatistics } from 'vs/platform/native/common/native';
import { homedir } from 'os';
export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = {
windowId: 0,
machineId: 'testMachineId',
sessionId: 'testSessionId',
logLevel: LogLevel.Error,
mainPid: 0,
partsSplashPath: '',
appRoot: '',
userEnv: {},
execPath: process.execPath,
perfEntries: [],
colorScheme: { dark: true, highContrast: false },
...parseArgs(process.argv, OPTIONS)
};
export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration, TestProductService);
export class TestTextFileService extends NativeTextFileService {
private resolveTextContentError!: FileOperationError | null;
constructor(
@IFileService protected fileService: IFileService,
@IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService,
@ILifecycleService lifecycleService: ILifecycleService,
@IInstantiationService instantiationService: IInstantiationService,
@IModelService modelService: IModelService,
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
@IDialogService dialogService: IDialogService,
@IFileDialogService fileDialogService: IFileDialogService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
@IProductService productService: IProductService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextModelService textModelService: ITextModelService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IPathService pathService: IPathService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@ILogService logService: ILogService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IModeService modeService: IModeService,
@INativeHostService nativeHostService: INativeHostService
) {
super(
fileService,
untitledTextEditorService,
lifecycleService,
instantiationService,
modelService,
environmentService,
dialogService,
fileDialogService,
textResourceConfigurationService,
filesConfigurationService,
textModelService,
codeEditorService,
pathService,
workingCopyFileService,
uriIdentityService,
modeService,
nativeHostService,
logService
);
}
setResolveTextContentErrorOnce(error: FileOperationError): void {
this.resolveTextContentError = error;
}
async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
if (this.resolveTextContentError) {
const error = this.resolveTextContentError;
this.resolveTextContentError = null;
throw error;
}
const content = await this.fileService.readFileStream(resource, options);
return {
resource: content.resource,
name: content.name,
mtime: content.mtime,
ctime: content.ctime,
etag: content.etag,
encoding: 'utf8',
value: await createTextBufferFactoryFromStream(content.value),
size: 10
};
}
}
export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService {
private _testEncoding: TestEncodingOracle | undefined;
get encoding(): TestEncodingOracle {
if (!this._testEncoding) {
this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle));
}
return this._testEncoding;
}
}
export class TestSharedProcessService implements ISharedProcessService {
declare readonly _serviceBrand: undefined;
getChannel(channelName: string): any { return undefined; }
registerChannel(channelName: string, channel: any): void { }
async toggleSharedProcessWindow(): Promise<void> { }
async whenSharedProcessReady(): Promise<void> { }
}
export class TestNativeHostService implements INativeHostService {
declare readonly _serviceBrand: undefined;
readonly windowId = -1;
onDidOpenWindow: Event<number> = Event.None;
onDidMaximizeWindow: Event<number> = Event.None;
onDidUnmaximizeWindow: Event<number> = Event.None;
onDidFocusWindow: Event<number> = Event.None;
onDidBlurWindow: Event<number> = Event.None;
onDidResumeOS: Event<unknown> = Event.None;
onDidChangeColorScheme = Event.None;
onDidChangePassword = Event.None;
windowCount = Promise.resolve(1);
getWindowCount(): Promise<number> { return this.windowCount; }
async getWindows(): Promise<IOpenedWindow[]> { return []; }
async getActiveWindowId(): Promise<number | undefined> { return undefined; }
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
throw new Error('Method not implemented.');
}
async toggleFullScreen(): Promise<void> { }
async handleTitleDoubleClick(): Promise<void> { }
async isMaximized(): Promise<boolean> { return true; }
async maximizeWindow(): Promise<void> { }
async unmaximizeWindow(): Promise<void> { }
async minimizeWindow(): Promise<void> { }
async setMinimumSize(width: number | undefined, height: number | undefined): Promise<void> { }
async focusWindow(options?: { windowId?: number | undefined; } | undefined): Promise<void> { }
async showMessageBox(options: Electron.MessageBoxOptions): Promise<Electron.MessageBoxReturnValue> { throw new Error('Method not implemented.'); }
async showSaveDialog(options: Electron.SaveDialogOptions): Promise<Electron.SaveDialogReturnValue> { throw new Error('Method not implemented.'); }
async showOpenDialog(options: Electron.OpenDialogOptions): Promise<Electron.OpenDialogReturnValue> { throw new Error('Method not implemented.'); }
async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> { }
async pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> { }
async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> { }
async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> { }
async showItemInFolder(path: string): Promise<void> { }
async setRepresentedFilename(path: string): Promise<void> { }
async isAdmin(): Promise<boolean> { return false; }
async writeElevated(source: URI, target: URI, options?: { overwriteReadonly?: boolean | undefined; }): Promise<void> { }
async getOSProperties(): Promise<IOSProperties> { return Object.create(null); }
async getOSStatistics(): Promise<IOSStatistics> { return Object.create(null); }
async getOSVirtualMachineHint(): Promise<number> { return 0; }
async killProcess(): Promise<void> { }
async setDocumentEdited(edited: boolean): Promise<void> { }
async openExternal(url: string): Promise<boolean> { return false; }
async updateTouchBar(): Promise<void> { }
async moveItemToTrash(): Promise<boolean> { return false; }
async newWindowTab(): Promise<void> { }
async showPreviousWindowTab(): Promise<void> { }
async showNextWindowTab(): Promise<void> { }
async moveWindowTabToNewWindow(): Promise<void> { }
async mergeAllWindowTabs(): Promise<void> { }
async toggleWindowTabsBar(): Promise<void> { }
async notifyReady(): Promise<void> { }
async relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; } | undefined): Promise<void> { }
async reload(): Promise<void> { }
async closeWindow(): Promise<void> { }
async closeWindowById(): Promise<void> { }
async quit(): Promise<void> { }
async exit(code: number): Promise<void> { }
async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise<void> { }
async toggleDevTools(): Promise<void> { }
async resolveProxy(url: string): Promise<string | undefined> { return undefined; }
async readClipboardText(type?: 'selection' | 'clipboard' | undefined): Promise<string> { return ''; }
async writeClipboardText(text: string, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
async readClipboardFindText(): Promise<string> { return ''; }
async writeClipboardFindText(text: string): Promise<void> { }
async writeClipboardBuffer(format: string, buffer: Uint8Array, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
async readClipboardBuffer(format: string): Promise<Uint8Array> { return Uint8Array.from([]); }
async hasClipboard(format: string, type?: 'selection' | 'clipboard' | undefined): Promise<boolean> { return false; }
async sendInputEvent(event: MouseInputEvent): Promise<void> { }
async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise<string | undefined> { return undefined; }
async getPassword(service: string, account: string): Promise<string | null> { return null; }
async setPassword(service: string, account: string, password: string): Promise<void> { }
async deletePassword(service: string, account: string): Promise<boolean> { return false; }
async findPassword(service: string): Promise<string | null> { return null; }
async findCredentials(service: string): Promise<{ account: string; password: string; }[]> { return []; }
}
export function workbenchInstantiationService(): ITestInstantiationService {
const instantiationService = browserWorkbenchInstantiationService({
textFileService: insta => <ITextFileService>insta.createInstance(TestTextFileService),
pathService: insta => <IPathService>insta.createInstance(TestNativePathService)
});
instantiationService.stub(INativeHostService, new TestNativeHostService());
instantiationService.stub(INativeWorkbenchEnvironmentService, TestEnvironmentService);
return instantiationService;
}
export class TestServiceAccessor {
constructor(
@ILifecycleService public lifecycleService: TestLifecycleService,
@ITextFileService public textFileService: TestTextFileService,
@IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService,
@IWorkspaceContextService public contextService: TestContextService,
@IModelService public modelService: ModelServiceImpl,
@IFileService public fileService: TestFileService,
@INativeHostService public nativeHostService: TestNativeHostService,
@IFileDialogService public fileDialogService: TestFileDialogService,
@IBackupFileService public backupFileService: NodeTestBackupFileService,
@IWorkingCopyService public workingCopyService: IWorkingCopyService,
@IEditorService public editorService: IEditorService
) {
}
}
export class TestNativePathService extends TestPathService {
declare readonly _serviceBrand: undefined;
constructor() {
super(URI.file(homedir()));
}
}