未验证 提交 af37dd91 编写于 作者: A Alexandru Dima 提交者: GitHub

Merge pull request #135602 from rchiodo/dev/rchiodo/multiple_providers

Allow semantic tokens to be provided by more than one provider
...@@ -23,30 +23,111 @@ export function isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): ...@@ -23,30 +23,111 @@ export function isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits):
return v && Array.isArray((<SemanticTokensEdits>v).edits); return v && Array.isArray((<SemanticTokensEdits>v).edits);
} }
export interface IDocumentSemanticTokensResult { export class DocumentSemanticTokensResult {
provider: DocumentSemanticTokensProvider; constructor(
request: Promise<SemanticTokens | SemanticTokensEdits | null | undefined>; public readonly provider: DocumentSemanticTokensProvider,
public readonly tokens: SemanticTokens | SemanticTokensEdits | null,
) { }
} }
export function getDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): IDocumentSemanticTokensResult | null { export function hasDocumentSemanticTokensProvider(model: ITextModel): boolean {
const provider = _getDocumentSemanticTokensProvider(model); return DocumentSemanticTokensProviderRegistry.has(model);
if (!provider) { }
return null;
function getDocumentSemanticTokensProviders(model: ITextModel): DocumentSemanticTokensProvider[] {
const groups = DocumentSemanticTokensProviderRegistry.orderedGroups(model);
return (groups.length > 0 ? groups[0] : []);
}
export async function getDocumentSemanticTokens(model: ITextModel, lastProvider: DocumentSemanticTokensProvider | null, lastResultId: string | null, token: CancellationToken): Promise<DocumentSemanticTokensResult | null> {
const providers = getDocumentSemanticTokensProviders(model);
// Get tokens from all providers at the same time.
const results = await Promise.all(providers.map(async (provider) => {
let result: SemanticTokens | SemanticTokensEdits | null | undefined;
try {
result = await provider.provideDocumentSemanticTokens(model, (provider === lastProvider ? lastResultId : null), token);
} catch (err) {
onUnexpectedExternalError(err);
result = null;
}
if (!result || (!isSemanticTokens(result) && !isSemanticTokensEdits(result))) {
result = null;
}
return new DocumentSemanticTokensResult(provider, result);
}));
// Try to return the first result with actual tokens
for (const result of results) {
if (result.tokens) {
return result;
}
}
// Return the first result, even if it doesn't have tokens
if (results.length > 0) {
return results[0];
} }
return {
provider: provider, return null;
request: Promise.resolve(provider.provideDocumentSemanticTokens(model, lastResultId, token))
};
} }
function _getDocumentSemanticTokensProvider(model: ITextModel): DocumentSemanticTokensProvider | null { function _getDocumentSemanticTokensProviderHighestGroup(model: ITextModel): DocumentSemanticTokensProvider[] | null {
const result = DocumentSemanticTokensProviderRegistry.ordered(model); const result = DocumentSemanticTokensProviderRegistry.orderedGroups(model);
return (result.length > 0 ? result[0] : null); return (result.length > 0 ? result[0] : null);
} }
export function getDocumentRangeSemanticTokensProvider(model: ITextModel): DocumentRangeSemanticTokensProvider | null { class DocumentRangeSemanticTokensResult {
const result = DocumentRangeSemanticTokensProviderRegistry.ordered(model); constructor(
return (result.length > 0 ? result[0] : null); public readonly provider: DocumentRangeSemanticTokensProvider,
public readonly tokens: SemanticTokens | null,
) { }
}
export function hasDocumentRangeSemanticTokensProvider(model: ITextModel): boolean {
return DocumentRangeSemanticTokensProviderRegistry.has(model);
}
function getDocumentRangeSemanticTokensProviders(model: ITextModel): DocumentRangeSemanticTokensProvider[] {
const groups = DocumentRangeSemanticTokensProviderRegistry.orderedGroups(model);
return (groups.length > 0 ? groups[0] : []);
}
export async function getDocumentRangeSemanticTokens(model: ITextModel, range: Range, token: CancellationToken): Promise<DocumentRangeSemanticTokensResult | null> {
const providers = getDocumentRangeSemanticTokensProviders(model);
// Get tokens from all providers at the same time.
const results = await Promise.all(providers.map(async (provider) => {
let result: SemanticTokens | null | undefined;
try {
result = await provider.provideDocumentRangeSemanticTokens(model, range, token);
} catch (err) {
onUnexpectedExternalError(err);
result = null;
}
if (!result || !isSemanticTokens(result)) {
result = null;
}
return new DocumentRangeSemanticTokensResult(provider, result);
}));
// Try to return the first result with actual tokens
for (const result of results) {
if (result.tokens) {
return result;
}
}
// Return the first result, even if it doesn't have tokens
if (results.length > 0) {
return results[0];
}
return null;
} }
CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => { CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => {
...@@ -58,13 +139,13 @@ CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async ( ...@@ -58,13 +139,13 @@ CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async (
return undefined; return undefined;
} }
const provider = _getDocumentSemanticTokensProvider(model); const providers = _getDocumentSemanticTokensProviderHighestGroup(model);
if (!provider) { if (!providers) {
// there is no provider => fall back to a document range semantic tokens provider // there is no provider => fall back to a document range semantic tokens provider
return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokensLegend', uri); return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokensLegend', uri);
} }
return provider.getLegend(); return providers[0].getLegend();
}); });
CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => { CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => {
...@@ -76,39 +157,35 @@ CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (access ...@@ -76,39 +157,35 @@ CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (access
return undefined; return undefined;
} }
const r = getDocumentSemanticTokens(model, null, CancellationToken.None); if (!hasDocumentSemanticTokensProvider(model)) {
if (!r) {
// there is no provider => fall back to a document range semantic tokens provider // there is no provider => fall back to a document range semantic tokens provider
return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokens', uri, model.getFullModelRange()); return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokens', uri, model.getFullModelRange());
} }
const { provider, request } = r; const r = await getDocumentSemanticTokens(model, null, null, CancellationToken.None);
if (!r) {
let result: SemanticTokens | SemanticTokensEdits | null | undefined;
try {
result = await request;
} catch (err) {
onUnexpectedExternalError(err);
return undefined; return undefined;
} }
if (!result || !isSemanticTokens(result)) { const { provider, tokens } = r;
if (!tokens || !isSemanticTokens(tokens)) {
return undefined; return undefined;
} }
const buff = encodeSemanticTokensDto({ const buff = encodeSemanticTokensDto({
id: 0, id: 0,
type: 'full', type: 'full',
data: result.data data: tokens.data
}); });
if (result.resultId) { if (tokens.resultId) {
provider.releaseDocumentSemanticTokens(result.resultId); provider.releaseDocumentSemanticTokens(tokens.resultId);
} }
return buff; return buff;
}); });
CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => { CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => {
const [uri] = args; const [uri, range] = args;
assertType(uri instanceof URI); assertType(uri instanceof URI);
const model = accessor.get(IModelService).getModel(uri); const model = accessor.get(IModelService).getModel(uri);
...@@ -116,12 +193,31 @@ CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', as ...@@ -116,12 +193,31 @@ CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', as
return undefined; return undefined;
} }
const provider = getDocumentRangeSemanticTokensProvider(model); const providers = getDocumentRangeSemanticTokensProviders(model);
if (!provider) { if (providers.length === 0) {
// no providers
return undefined;
}
if (providers.length === 1) {
// straight forward case, just a single provider
return providers[0].getLegend();
}
if (!range || !Range.isIRange(range)) {
// if no range is provided, we cannot support multiple providers
// as we cannot fall back to the one which would give results
// => return the first legend for backwards compatibility and print a warning
console.warn(`provideDocumentRangeSemanticTokensLegend might be out-of-sync with provideDocumentRangeSemanticTokens unless a range argument is passed in`);
return providers[0].getLegend();
}
const result = await getDocumentRangeSemanticTokens(model, Range.lift(range), CancellationToken.None);
if (!result) {
return undefined; return undefined;
} }
return provider.getLegend(); return result.provider.getLegend();
}); });
CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => { CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => {
...@@ -134,27 +230,15 @@ CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (a ...@@ -134,27 +230,15 @@ CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (a
return undefined; return undefined;
} }
const provider = getDocumentRangeSemanticTokensProvider(model); const result = await getDocumentRangeSemanticTokens(model, Range.lift(range), CancellationToken.None);
if (!provider) { if (!result || !result.tokens) {
// there is no provider // there is no provider or it didn't return tokens
return undefined;
}
let result: SemanticTokens | null | undefined;
try {
result = await provider.provideDocumentRangeSemanticTokens(model, Range.lift(range), CancellationToken.None);
} catch (err) {
onUnexpectedExternalError(err);
return undefined;
}
if (!result || !isSemanticTokens(result)) {
return undefined; return undefined;
} }
return encodeSemanticTokensDto({ return encodeSemanticTokensDto({
id: 0, id: 0,
type: 'full', type: 'full',
data: result.data data: result.tokens.data
}); });
}); });
...@@ -29,7 +29,7 @@ import { StringSHA1 } from 'vs/base/common/hash'; ...@@ -29,7 +29,7 @@ import { StringSHA1 } from 'vs/base/common/hash';
import { EditStackElement, isEditStackElement } from 'vs/editor/common/model/editStack'; import { EditStackElement, isEditStackElement } from 'vs/editor/common/model/editStack';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
import { getDocumentSemanticTokens, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/common/services/getSemanticTokens'; import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/common/services/getSemanticTokens';
import { equals } from 'vs/base/common/objects'; import { equals } from 'vs/base/common/objects';
import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ILanguageConfigurationService } from 'vs/editor/common/modes/languageConfigurationRegistry';
...@@ -724,13 +724,13 @@ class SemanticStyling extends Disposable { ...@@ -724,13 +724,13 @@ class SemanticStyling extends Disposable {
class SemanticTokensResponse { class SemanticTokensResponse {
constructor( constructor(
private readonly _provider: DocumentSemanticTokensProvider, public readonly provider: DocumentSemanticTokensProvider,
public readonly resultId: string | undefined, public readonly resultId: string | undefined,
public readonly data: Uint32Array public readonly data: Uint32Array
) { } ) { }
public dispose(): void { public dispose(): void {
this._provider.releaseDocumentSemanticTokens(this.resultId); this.provider.releaseDocumentSemanticTokens(this.resultId);
} }
} }
...@@ -820,10 +820,7 @@ export class ModelSemanticColoring extends Disposable { ...@@ -820,10 +820,7 @@ export class ModelSemanticColoring extends Disposable {
return; return;
} }
const cancellationTokenSource = new CancellationTokenSource(); if (!hasDocumentSemanticTokensProvider(this._model)) {
const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null;
const r = getDocumentSemanticTokens(this._model, lastResultId, cancellationTokenSource.token);
if (!r) {
// there is no provider // there is no provider
if (this._currentDocumentResponse) { if (this._currentDocumentResponse) {
// there are semantic tokens set // there are semantic tokens set
...@@ -832,7 +829,10 @@ export class ModelSemanticColoring extends Disposable { ...@@ -832,7 +829,10 @@ export class ModelSemanticColoring extends Disposable {
return; return;
} }
const { provider, request } = r; const cancellationTokenSource = new CancellationTokenSource();
const lastProvider = this._currentDocumentResponse ? this._currentDocumentResponse.provider : null;
const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null;
const request = getDocumentSemanticTokens(this._model, lastProvider, lastResultId, cancellationTokenSource.token);
this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource; this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource;
const pendingChanges: IModelContentChangedEvent[] = []; const pendingChanges: IModelContentChangedEvent[] = [];
...@@ -840,12 +840,17 @@ export class ModelSemanticColoring extends Disposable { ...@@ -840,12 +840,17 @@ export class ModelSemanticColoring extends Disposable {
pendingChanges.push(e); pendingChanges.push(e);
}); });
const styling = this._semanticStyling.get(provider);
request.then((res) => { request.then((res) => {
this._currentDocumentRequestCancellationTokenSource = null; this._currentDocumentRequestCancellationTokenSource = null;
contentChangeListener.dispose(); contentChangeListener.dispose();
this._setDocumentSemanticTokens(provider, res || null, styling, pendingChanges);
if (!res) {
this._setDocumentSemanticTokens(null, null, null, pendingChanges);
} else {
const { provider, tokens } = res;
const styling = this._semanticStyling.get(provider);
this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges);
}
}, (err) => { }, (err) => {
const isExpectedError = err && (errors.isPromiseCanceledError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1)); const isExpectedError = err && (errors.isPromiseCanceledError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1));
if (!isExpectedError) { if (!isExpectedError) {
......
...@@ -10,11 +10,11 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; ...@@ -10,11 +10,11 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { DocumentRangeSemanticTokensProvider, DocumentRangeSemanticTokensProviderRegistry, SemanticTokens } from 'vs/editor/common/modes'; import { DocumentRangeSemanticTokensProviderRegistry } from 'vs/editor/common/modes';
import { getDocumentRangeSemanticTokensProvider } from 'vs/editor/common/services/getSemanticTokens'; import { getDocumentRangeSemanticTokens, hasDocumentRangeSemanticTokensProvider } from 'vs/editor/common/services/getSemanticTokens';
import { IModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/modelService';
import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/common/services/modelServiceImpl'; import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from 'vs/editor/common/services/modelServiceImpl';
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
...@@ -28,7 +28,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo ...@@ -28,7 +28,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo
private readonly _editor: ICodeEditor; private readonly _editor: ICodeEditor;
private readonly _tokenizeViewport: RunOnceScheduler; private readonly _tokenizeViewport: RunOnceScheduler;
private _outstandingRequests: CancelablePromise<SemanticTokens | null | undefined>[]; private _outstandingRequests: CancelablePromise<any>[];
constructor( constructor(
editor: ICodeEditor, editor: ICodeEditor,
...@@ -74,7 +74,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo ...@@ -74,7 +74,7 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo
this._outstandingRequests = []; this._outstandingRequests = [];
} }
private _removeOutstandingRequest(req: CancelablePromise<SemanticTokens | null | undefined>): void { private _removeOutstandingRequest(req: CancelablePromise<any>): void {
for (let i = 0, len = this._outstandingRequests.length; i < len; i++) { for (let i = 0, len = this._outstandingRequests.length; i < len; i++) {
if (this._outstandingRequests[i] === req) { if (this._outstandingRequests[i] === req) {
this._outstandingRequests.splice(i, 1); this._outstandingRequests.splice(i, 1);
...@@ -97,27 +97,27 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo ...@@ -97,27 +97,27 @@ class ViewportSemanticTokensContribution extends Disposable implements IEditorCo
} }
return; return;
} }
const provider = getDocumentRangeSemanticTokensProvider(model); if (!hasDocumentRangeSemanticTokensProvider(model)) {
if (!provider) {
if (model.hasSomeSemanticTokens()) { if (model.hasSomeSemanticTokens()) {
model.setSemanticTokens(null, false); model.setSemanticTokens(null, false);
} }
return; return;
} }
const styling = this._modelService.getSemanticTokensProviderStyling(provider);
const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow(); const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow();
this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range, provider, styling))); this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range)));
} }
private _requestRange(model: ITextModel, range: Range, provider: DocumentRangeSemanticTokensProvider, styling: SemanticTokensProviderStyling): CancelablePromise<SemanticTokens | null | undefined> { private _requestRange(model: ITextModel, range: Range): CancelablePromise<any> {
const requestVersionId = model.getVersionId(); const requestVersionId = model.getVersionId();
const request = createCancelablePromise(token => Promise.resolve(provider.provideDocumentRangeSemanticTokens(model, range, token))); const request = createCancelablePromise(token => Promise.resolve(getDocumentRangeSemanticTokens(model, range, token)));
request.then((r) => { request.then((r) => {
if (!r || model.isDisposed() || model.getVersionId() !== requestVersionId) { if (!r || !r.tokens || model.isDisposed() || model.getVersionId() !== requestVersionId) {
return; return;
} }
model.setPartialSemanticTokens(range, toMultilineTokens2(r, styling, model.getLanguageId())); const { provider, tokens: result } = r;
const styling = this._modelService.getSemanticTokensProviderStyling(provider);
model.setPartialSemanticTokens(range, toMultilineTokens2(result, styling, model.getLanguageId()));
}).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request)); }).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request));
return request; return request;
} }
......
...@@ -32,6 +32,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; ...@@ -32,6 +32,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/common/services/getSemanticTokens';
const GENERATE_TESTS = false; const GENERATE_TESTS = false;
...@@ -486,6 +487,81 @@ suite('ModelSemanticColoring', () => { ...@@ -486,6 +487,81 @@ suite('ModelSemanticColoring', () => {
// assert that it got called twice // assert that it got called twice
assert.strictEqual(callCount, 2); assert.strictEqual(callCount, 2);
}); });
test('DocumentSemanticTokens should be pick the token provider with actual items', async () => {
let callCount = 0;
disposables.add(ModesRegistry.registerLanguage({ id: 'testMode2' }));
disposables.add(DocumentSemanticTokensProviderRegistry.register('testMode2', new class implements DocumentSemanticTokensProvider {
getLegend(): SemanticTokensLegend {
return { tokenTypes: ['class1'], tokenModifiers: [] };
}
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
callCount++;
// For a secondary request return a different value
if (lastResultId) {
return {
data: new Uint32Array([2, 1, 1, 1, 1, 0, 2, 1, 1, 1])
};
}
return {
resultId: '1',
data: new Uint32Array([0, 1, 1, 1, 1, 0, 2, 1, 1, 1])
};
}
releaseDocumentSemanticTokens(resultId: string | undefined): void {
}
}));
disposables.add(DocumentSemanticTokensProviderRegistry.register('testMode2', new class implements DocumentSemanticTokensProvider {
getLegend(): SemanticTokensLegend {
return { tokenTypes: ['class2'], tokenModifiers: [] };
}
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
callCount++;
return null;
}
releaseDocumentSemanticTokens(resultId: string | undefined): void {
}
}));
function toArr(arr: Uint32Array): number[] {
let result: number[] = [];
for (let i = 0; i < arr.length; i++) {
result[i] = arr[i];
}
return result;
}
const textModel = modelService.createModel('Hello world 2', modeService.create('testMode2'));
try {
let result = await getDocumentSemanticTokens(textModel, null, null, CancellationToken.None);
assert.ok(result, `We should have tokens (1)`);
assert.ok(result.tokens, `Tokens are found from multiple providers (1)`);
assert.ok(isSemanticTokens(result.tokens), `Tokens are full (1)`);
assert.ok(result.tokens.resultId, `Token result id found from multiple providers (1)`);
assert.deepStrictEqual(toArr(result.tokens.data), [0, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (1)`);
assert.deepStrictEqual(callCount, 2, `Called both token providers (1)`);
assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (1)`);
// Make a second request. Make sure we get the secondary value
result = await getDocumentSemanticTokens(textModel, result.provider, result.tokens.resultId, CancellationToken.None);
assert.ok(result, `We should have tokens (2)`);
assert.ok(result.tokens, `Tokens are found from multiple providers (2)`);
assert.ok(isSemanticTokens(result.tokens), `Tokens are full (2)`);
assert.ok(!result.tokens.resultId, `Token result id found from multiple providers (2)`);
assert.deepStrictEqual(toArr(result.tokens.data), [2, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (2)`);
assert.deepStrictEqual(callCount, 4, `Called both token providers (2)`);
assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (2)`);
} finally {
disposables.clear();
// Wait for scheduler to finish
await timeout(0);
// Now dispose the text model
textModel.dispose();
}
});
}); });
function assertComputeEdits(lines1: string[], lines2: string[]): void { function assertComputeEdits(lines1: string[], lines2: string[]): void {
......
...@@ -216,7 +216,7 @@ const newCommands: ApiCommand[] = [ ...@@ -216,7 +216,7 @@ const newCommands: ApiCommand[] = [
), ),
new ApiCommand( new ApiCommand(
'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range', 'vscode.provideDocumentRangeSemanticTokensLegend', '_provideDocumentRangeSemanticTokensLegend', 'Provide semantic tokens legend for a document range',
[ApiCommandArgument.Uri], [ApiCommandArgument.Uri, ApiCommandArgument.Range.optional()],
new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => { new ApiCommandResult<modes.SemanticTokensLegend, types.SemanticTokensLegend | undefined>('A promise that resolves to SemanticTokensLegend.', value => {
if (!value) { if (!value) {
return undefined; return undefined;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册