diff --git a/src/vs/editor/common/languageFeatureRegistry.ts b/src/vs/editor/common/languageFeatureRegistry.ts index cc6f891cf71..ea3c4e6aee1 100644 --- a/src/vs/editor/common/languageFeatureRegistry.ts +++ b/src/vs/editor/common/languageFeatureRegistry.ts @@ -7,6 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITextModel, shouldSynchronizeModel } from 'vs/editor/common/model'; import { LanguageFilter, LanguageSelector, score } from 'vs/editor/common/languageSelector'; +import { URI } from 'vs/base/common/uri'; interface Entry { selector: LanguageSelector; @@ -25,7 +26,9 @@ function isExclusive(selector: LanguageSelector): boolean { } } -export type ScoreFunction = typeof score; +export interface NotebooTypeResolver { + (uri: URI): string | undefined; +} export class LanguageFeatureRegistry { @@ -35,7 +38,7 @@ export class LanguageFeatureRegistry { private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - constructor(private readonly _scoreFn: ScoreFunction = score) { } + constructor(private readonly _notebookTypeResolver?: NotebooTypeResolver) { } register(selector: LanguageSelector, provider: T): IDisposable { @@ -124,18 +127,23 @@ export class LanguageFeatureRegistry { } } - private _lastCandidate: { uri: string; language: string } | undefined; + private _lastCandidate: { uri: string; language: string; notebookType?: string } | undefined; private _updateScores(model: ITextModel): void { + const notebookType = this._notebookTypeResolver?.(model.uri); + const candidate = { uri: model.uri.toString(), - language: model.getLanguageId() + language: model.getLanguageId(), + notebookType }; if (this._lastCandidate && this._lastCandidate.language === candidate.language - && this._lastCandidate.uri === candidate.uri) { + && this._lastCandidate.uri === candidate.uri + && this._lastCandidate.notebookType === candidate.notebookType + ) { // nothing has changed return; @@ -144,7 +152,7 @@ export class LanguageFeatureRegistry { this._lastCandidate = candidate; for (let entry of this._entries) { - entry._score = this._scoreFn(entry.selector, model.uri, model.getLanguageId(), shouldSynchronizeModel(model)); + entry._score = score(entry.selector, model.uri, model.getLanguageId(), shouldSynchronizeModel(model), notebookType); if (isExclusive(entry.selector) && entry._score > 0) { // support for one exclusive selector that overwrites diff --git a/src/vs/editor/common/languageSelector.ts b/src/vs/editor/common/languageSelector.ts index df597ac100e..2ef0be7ee10 100644 --- a/src/vs/editor/common/languageSelector.ts +++ b/src/vs/editor/common/languageSelector.ts @@ -21,13 +21,13 @@ export interface LanguageFilter { export type LanguageSelector = string | LanguageFilter | ReadonlyArray; -export function score(selector: LanguageSelector | undefined, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean): number { +export function score(selector: LanguageSelector | undefined, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean, candidateNotebookType: string | undefined): number { if (Array.isArray(selector)) { // array -> take max individual value let ret = 0; for (const filter of selector) { - const value = score(filter, candidateUri, candidateLanguage, candidateIsSynchronized); + const value = score(filter, candidateUri, candidateLanguage, candidateIsSynchronized, candidateNotebookType); if (value === 10) { return value; // already at the highest } @@ -56,7 +56,7 @@ export function score(selector: LanguageSelector | undefined, candidateUri: URI, } else if (selector) { // filter -> select accordingly, use defaults for scheme - const { language, pattern, scheme, hasAccessToAllModels } = selector as LanguageFilter; // TODO: microsoft/TypeScript#42768 + const { language, pattern, scheme, hasAccessToAllModels, notebookType } = selector as LanguageFilter; // TODO: microsoft/TypeScript#42768 if (!candidateIsSynchronized && !hasAccessToAllModels) { return 0; @@ -84,6 +84,16 @@ export function score(selector: LanguageSelector | undefined, candidateUri: URI, } } + if (notebookType) { + if (notebookType === candidateNotebookType) { + ret = 10; + } else if (notebookType === '*') { + ret = Math.max(ret, 5); + } else { + return 0; + } + } + if (pattern) { let normalizedPattern: string | IRelativePattern; if (typeof pattern === 'string') { diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index e650b3c14b1..965d06db477 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -3,16 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { LanguageFeatureRegistry, NotebooTypeResolver } from 'vs/editor/common/languageFeatureRegistry'; import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export interface RefineScoreFunction { - (baseScore: number, selector: LanguageSelector, candidateUri: URI, candidateLanguage: string): number; -} - export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); export interface ILanguageFeaturesService { @@ -75,5 +69,5 @@ export interface ILanguageFeaturesService { // -- - setScoreRefineFunction(fn: RefineScoreFunction | undefined): void; + setNotebookTypeResolver(resolver: NotebooTypeResolver | undefined): void; } diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index d9cc69c68f7..294bae8feef 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { LanguageFeatureRegistry, NotebooTypeResolver } from 'vs/editor/common/languageFeatureRegistry'; import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; -import { LanguageSelector, score } from 'vs/editor/common/languageSelector'; -import { ILanguageFeaturesService, RefineScoreFunction } from 'vs/editor/common/services/languageFeatures'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class LanguageFeaturesService implements ILanguageFeaturesService { @@ -42,26 +41,15 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly documentRangeSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); - private _refinedScore?: RefineScoreFunction; - setScoreRefineFunction(fn: RefineScoreFunction | undefined): void { - this._refinedScore = fn; + private _notebookTypeResolver?: NotebooTypeResolver; + + setNotebookTypeResolver(resolver: NotebooTypeResolver | undefined) { + this._notebookTypeResolver = resolver; } - private _score(selector: LanguageSelector | undefined, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean) { - const base = score(selector, candidateUri, candidateLanguage, candidateIsSynchronized); - if (!selector) { - return base; - } - if (!this._refinedScore) { - return base; - } - const refined = this._refinedScore(base, selector, candidateUri, candidateLanguage); - if (refined === 0) { - return 0; - } else { - return Math.max(refined, base); - } + private _score(uri: URI): string | undefined { + return this._notebookTypeResolver?.(uri); } } diff --git a/src/vs/editor/test/common/modes/languageSelector.test.ts b/src/vs/editor/test/common/modes/languageSelector.test.ts index 1bbcd33884f..2939bfc0c62 100644 --- a/src/vs/editor/test/common/modes/languageSelector.test.ts +++ b/src/vs/editor/test/common/modes/languageSelector.test.ts @@ -15,18 +15,18 @@ suite('LanguageSelector', function () { }; test('score, invalid selector', function () { - assert.strictEqual(score({}, model.uri, model.language, true), 0); - assert.strictEqual(score(undefined!, model.uri, model.language, true), 0); - assert.strictEqual(score(null!, model.uri, model.language, true), 0); - assert.strictEqual(score('', model.uri, model.language, true), 0); + assert.strictEqual(score({}, model.uri, model.language, true, undefined), 0); + assert.strictEqual(score(undefined!, model.uri, model.language, true, undefined), 0); + assert.strictEqual(score(null!, model.uri, model.language, true, undefined), 0); + assert.strictEqual(score('', model.uri, model.language, true, undefined), 0); }); test('score, any language', function () { - assert.strictEqual(score({ language: '*' }, model.uri, model.language, true), 5); - assert.strictEqual(score('*', model.uri, model.language, true), 5); + assert.strictEqual(score({ language: '*' }, model.uri, model.language, true, undefined), 5); + assert.strictEqual(score('*', model.uri, model.language, true, undefined), 5); - assert.strictEqual(score('*', URI.parse('foo:bar'), model.language, true), 5); - assert.strictEqual(score('farboo', URI.parse('foo:bar'), model.language, true), 10); + assert.strictEqual(score('*', URI.parse('foo:bar'), model.language, true, undefined), 5); + assert.strictEqual(score('farboo', URI.parse('foo:bar'), model.language, true, undefined), 10); }); test('score, default schemes', function () { @@ -34,50 +34,50 @@ suite('LanguageSelector', function () { const uri = URI.parse('git:foo/file.txt'); const language = 'farboo'; - assert.strictEqual(score('*', uri, language, true), 5); - assert.strictEqual(score('farboo', uri, language, true), 10); - assert.strictEqual(score({ language: 'farboo', scheme: '' }, uri, language, true), 10); - assert.strictEqual(score({ language: 'farboo', scheme: 'git' }, uri, language, true), 10); - assert.strictEqual(score({ language: 'farboo', scheme: '*' }, uri, language, true), 10); - assert.strictEqual(score({ language: 'farboo' }, uri, language, true), 10); - assert.strictEqual(score({ language: '*' }, uri, language, true), 5); + assert.strictEqual(score('*', uri, language, true, undefined), 5); + assert.strictEqual(score('farboo', uri, language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo', scheme: '' }, uri, language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'git' }, uri, language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo', scheme: '*' }, uri, language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo' }, uri, language, true, undefined), 10); + assert.strictEqual(score({ language: '*' }, uri, language, true, undefined), 5); - assert.strictEqual(score({ scheme: '*' }, uri, language, true), 5); - assert.strictEqual(score({ scheme: 'git' }, uri, language, true), 10); + assert.strictEqual(score({ scheme: '*' }, uri, language, true, undefined), 5); + assert.strictEqual(score({ scheme: 'git' }, uri, language, true, undefined), 10); }); test('score, filter', function () { - assert.strictEqual(score('farboo', model.uri, model.language, true), 10); - assert.strictEqual(score({ language: 'farboo' }, model.uri, model.language, true), 10); - assert.strictEqual(score({ language: 'farboo', scheme: 'file' }, model.uri, model.language, true), 10); - assert.strictEqual(score({ language: 'farboo', scheme: 'http' }, model.uri, model.language, true), 0); + assert.strictEqual(score('farboo', model.uri, model.language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo' }, model.uri, model.language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'file' }, model.uri, model.language, true, undefined), 10); + assert.strictEqual(score({ language: 'farboo', scheme: 'http' }, model.uri, model.language, true, undefined), 0); - assert.strictEqual(score({ pattern: '**/*.fb' }, model.uri, model.language, true), 10); - assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'file' }, model.uri, model.language, true), 10); - assert.strictEqual(score({ pattern: '**/*.fb' }, URI.parse('foo:bar'), model.language, true), 0); - assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'foo' }, URI.parse('foo:bar'), model.language, true), 0); + assert.strictEqual(score({ pattern: '**/*.fb' }, model.uri, model.language, true, undefined), 10); + assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'file' }, model.uri, model.language, true, undefined), 10); + assert.strictEqual(score({ pattern: '**/*.fb' }, URI.parse('foo:bar'), model.language, true, undefined), 0); + assert.strictEqual(score({ pattern: '**/*.fb', scheme: 'foo' }, URI.parse('foo:bar'), model.language, true, undefined), 0); let doc = { uri: URI.parse('git:/my/file.js'), langId: 'javascript' }; - assert.strictEqual(score('javascript', doc.uri, doc.langId, true), 10); // 0; - assert.strictEqual(score({ language: 'javascript', scheme: 'git' }, doc.uri, doc.langId, true), 10); // 10; - assert.strictEqual(score('*', doc.uri, doc.langId, true), 5); // 5 - assert.strictEqual(score('fooLang', doc.uri, doc.langId, true), 0); // 0 - assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, true), 5); // 5 + assert.strictEqual(score('javascript', doc.uri, doc.langId, true, undefined), 10); // 0; + assert.strictEqual(score({ language: 'javascript', scheme: 'git' }, doc.uri, doc.langId, true, undefined), 10); // 10; + assert.strictEqual(score('*', doc.uri, doc.langId, true, undefined), 5); // 5 + assert.strictEqual(score('fooLang', doc.uri, doc.langId, true, undefined), 0); // 0 + assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, true, undefined), 5); // 5 }); test('score, max(filters)', function () { let match = { language: 'farboo', scheme: 'file' }; let fail = { language: 'farboo', scheme: 'http' }; - assert.strictEqual(score(match, model.uri, model.language, true), 10); - assert.strictEqual(score(fail, model.uri, model.language, true), 0); - assert.strictEqual(score([match, fail], model.uri, model.language, true), 10); - assert.strictEqual(score([fail, fail], model.uri, model.language, true), 0); - assert.strictEqual(score(['farboo', '*'], model.uri, model.language, true), 10); - assert.strictEqual(score(['*', 'farboo'], model.uri, model.language, true), 10); + assert.strictEqual(score(match, model.uri, model.language, true, undefined), 10); + assert.strictEqual(score(fail, model.uri, model.language, true, undefined), 0); + assert.strictEqual(score([match, fail], model.uri, model.language, true, undefined), 10); + assert.strictEqual(score([fail, fail], model.uri, model.language, true, undefined), 0); + assert.strictEqual(score(['farboo', '*'], model.uri, model.language, true, undefined), 10); + assert.strictEqual(score(['*', 'farboo'], model.uri, model.language, true, undefined), 10); }); test('score hasAccessToAllModels', function () { @@ -85,14 +85,14 @@ suite('LanguageSelector', function () { uri: URI.parse('file:/my/file.js'), langId: 'javascript' }; - assert.strictEqual(score('javascript', doc.uri, doc.langId, false), 0); - assert.strictEqual(score({ language: 'javascript', scheme: 'file' }, doc.uri, doc.langId, false), 0); - assert.strictEqual(score('*', doc.uri, doc.langId, false), 0); - assert.strictEqual(score('fooLang', doc.uri, doc.langId, false), 0); - assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, false), 0); + assert.strictEqual(score('javascript', doc.uri, doc.langId, false, undefined), 0); + assert.strictEqual(score({ language: 'javascript', scheme: 'file' }, doc.uri, doc.langId, false, undefined), 0); + assert.strictEqual(score('*', doc.uri, doc.langId, false, undefined), 0); + assert.strictEqual(score('fooLang', doc.uri, doc.langId, false, undefined), 0); + assert.strictEqual(score(['fooLang', '*'], doc.uri, doc.langId, false, undefined), 0); - assert.strictEqual(score({ language: 'javascript', scheme: 'file', hasAccessToAllModels: true }, doc.uri, doc.langId, false), 10); - assert.strictEqual(score(['fooLang', '*', { language: '*', hasAccessToAllModels: true }], doc.uri, doc.langId, false), 5); + assert.strictEqual(score({ language: 'javascript', scheme: 'file', hasAccessToAllModels: true }, doc.uri, doc.langId, false, undefined), 10); + assert.strictEqual(score(['fooLang', '*', { language: '*', hasAccessToAllModels: true }], doc.uri, doc.langId, false, undefined), 5); }); test('Document selector match - unexpected result value #60232', function () { @@ -101,7 +101,7 @@ suite('LanguageSelector', function () { scheme: 'file', pattern: '**/*.interface.json' }; - let value = score(selector, URI.parse('file:///C:/Users/zlhe/Desktop/test.interface.json'), 'json', true); + let value = score(selector, URI.parse('file:///C:/Users/zlhe/Desktop/test.interface.json'), 'json', true, undefined); assert.strictEqual(value, 10); }); @@ -112,7 +112,7 @@ suite('LanguageSelector', function () { pattern: '*.json' } }; - let value = score(selector, URI.file('/home/user/Desktop/test.json'), 'json', true); + let value = score(selector, URI.file('/home/user/Desktop/test.json'), 'json', true, undefined); assert.strictEqual(value, 10); }); }); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e801af2c03c..7232f9e43d4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -423,7 +423,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguages.changeLanguage(document.uri, languageId); }, match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number { - return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true); + return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true, document.notebook?.notebookType); }, registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 68e2df69964..21c082ba14a 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -74,7 +74,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD const cellLengths: number[] = []; const cellLineCounts: number[] = []; for (const cell of this._notebook.getCells()) { - if (cell.kind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.document.uri, cell.document.languageId, true))) { + if (cell.kind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.document.uri, cell.document.languageId, true, undefined))) { this._cellUris.set(cell.document.uri, this._cells.length); this._cells.push(cell); cellLengths.push(cell.document.getText().length + 1); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b198be1a15a..94cff1a6476 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -106,7 +106,6 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { LanguageFilter, LanguageSelector } from 'vs/editor/common/languageSelector'; /*--------------------------------------------------------------------------------------------- */ @@ -623,46 +622,7 @@ class NotebookLanguageSelectorScoreRefine { @INotebookService private readonly _notebookService: INotebookService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { - languageFeaturesService.setScoreRefineFunction(this._scoreNotebook.bind(this)); - } - - private _scoreNotebook(baseScore: number, selector: LanguageSelector, candidateUri: URI): number { - - if (Array.isArray(selector)) { - // array -> take max individual value - let ret = 0; - for (const filter of selector) { - const value = this._scoreNotebook(baseScore, filter, candidateUri); - if (value === 10) { - return value; // already at the highest - } - if (value > ret) { - ret = value; - } - } - return ret; - - } else if (typeof selector === 'string') { - // string defaults to { languageId} -> no possibility to express notebook type - return baseScore; - - } else { - // check for notebookType-selector -> makes this more strict - const { notebookType } = selector as LanguageFilter; - if (notebookType === undefined) { - return baseScore; - } - const candidateType = this._getNotebookType(candidateUri); - if (!candidateType) { - return 0; // wanted notebook but isn't notebook... - } else if (notebookType === '*') { - return 5; // any notebook type - } else if (notebookType === candidateType) { - return 10; - } else { - return 0; - } - } + languageFeaturesService.setNotebookTypeResolver(this._getNotebookType.bind(this)); } private _getNotebookType(uri: URI): string | undefined {