Skip to content

Commit

Permalink
Debt - Hook up a generic textual document highlight provider for sing…
Browse files Browse the repository at this point in the history
…le 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
  • Loading branch information
Yoyokrazy authored Aug 6, 2024
1 parent e745f44 commit 93f019c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<DocumentHighlight[]> {
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<ResourceMap<DocumentHighlight[]>> {

const result = new ResourceMap<DocumentHighlight[]>();
Expand Down Expand Up @@ -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()));
}
}
89 changes: 13 additions & 76 deletions src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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<boolean>('hasWordHighlights', false);

Expand All @@ -44,11 +43,12 @@ export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry<Docum

// 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 value)
// (result of size == 0 is valid, no highlights is a valid/expected result -- not a signal to fall back to other providers)
return first<DocumentHighlight[] | null | undefined>(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<DocumentHighlight[]>();
map.set(model.uri, result);
Expand All @@ -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<ResourceMap<DocumentHighlight[]> | 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<DocumentHighlight[]> | null | undefined): t is ResourceMap<DocumentHighlight[]> => t instanceof ResourceMap && t.size > 0);
}), (result): result is ResourceMap<DocumentHighlight[]> => result !== undefined && result !== null);
}

interface IOccurenceAtPositionRequest {
Expand Down Expand Up @@ -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<ResourceMap<DocumentHighlight[]>> {
return timeout(250, token).then(() => {
const result = new ResourceMap<DocumentHighlight[]>();

let wordResult;
if (this._word) {
wordResult = this._word;
} else {
wordResult = model.getWordAtPosition(selection.getPosition());
}

if (!wordResult) {
return new ResourceMap<DocumentHighlight[]>();
}

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<DocumentHighlightProvider>, 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<MultiDocumentHighlightProvider>, 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) => {
Expand Down Expand Up @@ -973,4 +910,4 @@ registerEditorContribution(WordHighlighterContribution.ID, WordHighlighterContri
registerEditorAction(NextWordHighlightAction);
registerEditorAction(PrevWordHighlightAction);
registerEditorAction(TriggerWordHighlightAction);
// registerEditorFeature(TextualMultiDocumentHighlightFeature);
registerEditorFeature(TextualMultiDocumentHighlightFeature);
5 changes: 3 additions & 2 deletions src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -321,7 +320,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
selector: selector,
provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise<Map<URI, languages.DocumentHighlight[]> | 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<languages.DocumentHighlight[]>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 93f019c

Please sign in to comment.