From 93f019cec14238bf2e9d2a4cdb532217b2d4c49b Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 6 Aug 2024 10:34:09 -0700 Subject: [PATCH] Debt - Hook up a generic textual document highlight provider for single and multi file settings (#224884) * textual occurrence provider as a generic highlight provider * `[]` is valid, so change to undefined in test w invalid occurrece value --- .../browser/textualHighlightProvider.ts | 29 +++++- .../browser/wordHighlighter.ts | 89 +++---------------- .../api/browser/mainThreadLanguageFeatures.ts | 5 +- .../browser/extHostLanguageFeatures.test.ts | 2 +- 4 files changed, 43 insertions(+), 82 deletions(-) diff --git a/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts b/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts index 1d7a65695d349..adc31ecd8c072 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts @@ -5,7 +5,7 @@ import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { DocumentHighlight, DocumentHighlightKind, MultiDocumentHighlightProvider, ProviderResult } from 'vs/editor/common/languages'; +import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider, MultiDocumentHighlightProvider, ProviderResult } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -14,10 +14,33 @@ import { ResourceMap } from 'vs/base/common/map'; import { LanguageFilter } from 'vs/editor/common/languageSelector'; -class TextualDocumentHighlightProvider implements MultiDocumentHighlightProvider { +class TextualDocumentHighlightProvider implements DocumentHighlightProvider, MultiDocumentHighlightProvider { selector: LanguageFilter = { language: '*' }; + provideDocumentHighlights(model: ITextModel, position: Position, token: CancellationToken): ProviderResult { + const result: DocumentHighlight[] = []; + + const word = model.getWordAtPosition({ + lineNumber: position.lineNumber, + column: position.column + }); + + if (!word) { + return Promise.resolve(result); + } + + if (model.isDisposed()) { + return; + } + + const matches = model.findMatches(word.word, true, false, true, USUAL_WORD_SEPARATORS, false); + return matches.map(m => ({ + range: m.range, + kind: DocumentHighlightKind.Text + })); + } + provideMultiDocumentHighlights(primaryModel: ITextModel, position: Position, otherModels: ITextModel[], token: CancellationToken): ProviderResult> { const result = new ResourceMap(); @@ -57,7 +80,7 @@ export class TextualMultiDocumentHighlightFeature extends Disposable { @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); - + this._register(languageFeaturesService.documentHighlightProvider.register('*', new TextualDocumentHighlightProvider())); this._register(languageFeaturesService.multiDocumentHighlightProvider.register('*', new TextualDocumentHighlightProvider())); } } diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index a566fd079de6d..6aa7ff831b6af 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as arrays from 'vs/base/common/arrays'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { CancelablePromise, createCancelablePromise, Delayer, first, timeout } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, Delayer, first } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -23,7 +22,7 @@ import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/commo import { IDiffEditor, IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider, MultiDocumentHighlightProvider } from 'vs/editor/common/languages'; +import { DocumentHighlight, DocumentHighlightProvider, MultiDocumentHighlightProvider } from 'vs/editor/common/languages'; import { IModelDeltaDecoration, ITextModel, shouldSynchronizeModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations'; @@ -34,8 +33,8 @@ import { Schemas } from 'vs/base/common/network'; import { ResourceMap } from 'vs/base/common/map'; import { score } from 'vs/editor/common/languageSelector'; import { isEqual } from 'vs/base/common/resources'; -// import { TextualMultiDocumentHighlightFeature } from 'vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider'; -// import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; +import { TextualMultiDocumentHighlightFeature } from 'vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider'; +import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; const ctxHasWordHighlights = new RawContextKey('hasWordHighlights', false); @@ -44,11 +43,12 @@ export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry(orderedByScore.map(provider => () => { return Promise.resolve(provider.provideDocumentHighlights(model, position, token)) .then(undefined, onUnexpectedExternalError); - }), arrays.isNonEmptyArray).then(result => { + }), (result): result is DocumentHighlight[] => result !== undefined && result !== null).then(result => { if (result) { const map = new ResourceMap(); map.set(model.uri, result); @@ -63,17 +63,17 @@ export function getOccurrencesAcrossMultipleModels(registry: LanguageFeatureRegi // in order of score ask the occurrences provider // until someone response with a good result - // (good = none empty array) + // (good = non undefined and non null ResourceMap) + // (result of size == 0 is valid, no highlights is a valid/expected result -- not a signal to fall back to other providers) return first | null | undefined>(orderedByScore.map(provider => () => { const filteredModels = otherModels.filter(otherModel => { return shouldSynchronizeModel(otherModel); }).filter(otherModel => { return score(provider.selector, otherModel.uri, otherModel.getLanguageId(), true, undefined, undefined) > 0; }); - return Promise.resolve(provider.provideMultiDocumentHighlights(model, position, filteredModels, token)) .then(undefined, onUnexpectedExternalError); - }), (t: ResourceMap | null | undefined): t is ResourceMap => t instanceof ResourceMap && t.size > 0); + }), (result): result is ResourceMap => result !== undefined && result !== null); } interface IOccurenceAtPositionRequest { @@ -184,76 +184,13 @@ class MultiModelOccurenceRequest extends OccurenceAtPositionRequest { } } -class TextualOccurenceRequest extends OccurenceAtPositionRequest { - - private readonly _otherModels: ITextModel[]; - private readonly _selectionIsEmpty: boolean; - private readonly _word: IWordAtPosition | null; - - constructor(model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string, otherModels: ITextModel[]) { - super(model, selection, wordSeparators); - this._otherModels = otherModels; - this._selectionIsEmpty = selection.isEmpty(); - this._word = word; - } - - protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise> { - return timeout(250, token).then(() => { - const result = new ResourceMap(); - - let wordResult; - if (this._word) { - wordResult = this._word; - } else { - wordResult = model.getWordAtPosition(selection.getPosition()); - } - - if (!wordResult) { - return new ResourceMap(); - } - - const allModels = [model, ...this._otherModels]; - - for (const otherModel of allModels) { - if (otherModel.isDisposed()) { - continue; - } - - const matches = otherModel.findMatches(wordResult.word, true, false, true, wordSeparators, false); - const highlights = matches.map(m => ({ - range: m.range, - kind: DocumentHighlightKind.Text - })); - - if (highlights) { - result.set(otherModel.uri, highlights); - } - } - return result; - }); - } - - public override isValid(model: ITextModel, selection: Selection, decorations: IEditorDecorationsCollection): boolean { - const currentSelectionIsEmpty = selection.isEmpty(); - if (this._selectionIsEmpty !== currentSelectionIsEmpty) { - return false; - } - return super.isValid(model, selection, decorations); - } -} function computeOccurencesAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string): IOccurenceAtPositionRequest { - if (registry.has(model)) { - return new SemanticOccurenceAtPositionRequest(model, selection, wordSeparators, registry); - } - return new TextualOccurenceRequest(model, selection, word, wordSeparators, []); + return new SemanticOccurenceAtPositionRequest(model, selection, wordSeparators, registry); } function computeOccurencesMultiModel(registry: LanguageFeatureRegistry, model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string, otherModels: ITextModel[]): IOccurenceAtPositionRequest { - if (registry.has(model)) { - return new MultiModelOccurenceRequest(model, selection, wordSeparators, registry, otherModels); - } - return new TextualOccurenceRequest(model, selection, word, wordSeparators, otherModels); + return new MultiModelOccurenceRequest(model, selection, wordSeparators, registry, otherModels); } registerModelAndPositionCommand('_executeDocumentHighlights', async (accessor, model, position) => { @@ -973,4 +910,4 @@ registerEditorContribution(WordHighlighterContribution.ID, WordHighlighterContri registerEditorAction(NextWordHighlightAction); registerEditorAction(PrevWordHighlightAction); registerEditorAction(TriggerWordHighlightAction); -// registerEditorFeature(TextualMultiDocumentHighlightFeature); +registerEditorFeature(TextualMultiDocumentHighlightFeature); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index c3d02eaf472cd..629af25ae4ce1 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { createStringDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from 'vs/base/common/dataTransfer'; @@ -321,7 +320,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread selector: selector, provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise | undefined> => { return this._proxy.$provideMultiDocumentHighlights(handle, model.uri, position, otherModels.map(model => model.uri), token).then(dto => { - if (isFalsyOrEmpty(dto)) { + // dto should be non-null + non-undefined + // dto length of 0 is valid, just no highlights, pass this through. + if (dto === undefined || dto === null) { return undefined; } const result = new ResourceMap(); diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index 141f114ebfab4..ec934301cd991 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -517,7 +517,7 @@ suite('ExtHostLanguageFeatures', function () { disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { - return []; + return undefined; } })); disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider {