mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-14 17:54:53 +01:00
Support Multi Editor/Model Highlighting (#196024)
* support multi-editor highlighting * Add experimental multi-document occurrences highlighting option * highlight accross notebook cells even without multi-doc setting * otherModels helper + ResourceMap + fixed removeDecorations * combine textual to just `TextualOccurenceRequest` * fix aggressive dispose/decoration clearing, introduce query interface
This commit is contained in:
@@ -562,6 +562,11 @@ export interface IEditorOptions {
|
||||
* Defaults to true.
|
||||
*/
|
||||
occurrencesHighlight?: boolean;
|
||||
/**
|
||||
* Enable semantic occurrences highlight.
|
||||
* Defaults to true.
|
||||
*/
|
||||
multiDocumentOccurrencesHighlight?: boolean;
|
||||
/**
|
||||
* Show code lens
|
||||
* Defaults to true.
|
||||
@@ -5116,6 +5121,7 @@ export const enum EditorOption {
|
||||
multiCursorModifier,
|
||||
multiCursorPaste,
|
||||
multiCursorLimit,
|
||||
multiDocumentOccurrencesHighlight,
|
||||
occurrencesHighlight,
|
||||
overviewRulerBorder,
|
||||
overviewRulerLanes,
|
||||
@@ -5616,6 +5622,10 @@ export const EditorOptions = {
|
||||
EditorOption.occurrencesHighlight, 'occurrencesHighlight', true,
|
||||
{ description: nls.localize('occurrencesHighlight', "Controls whether the editor should highlight semantic symbol occurrences.") }
|
||||
)),
|
||||
multiDocumentOccurrencesHighlight: register(new EditorBooleanOption(
|
||||
EditorOption.multiDocumentOccurrencesHighlight, 'multiDocumentOccurrencesHighlight', false,
|
||||
{ description: nls.localize('multiDocumentOccurrencesHighlight', "Experimental: Controls whether the editor should highlight word occurrences accross multiple open editors.") }
|
||||
)),
|
||||
overviewRulerBorder: register(new EditorBooleanOption(
|
||||
EditorOption.overviewRulerBorder, 'overviewRulerBorder', true,
|
||||
{ description: nls.localize('overviewRulerBorder', "Controls whether a border should be drawn around the overview ruler.") }
|
||||
|
||||
@@ -965,6 +965,17 @@ export interface DocumentHighlightProvider {
|
||||
provideDocumentHighlights(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||
}
|
||||
|
||||
export interface MultiDocumentHighlightProvider {
|
||||
/**
|
||||
* Provide a Map of URI --> document highlights, like all occurrences of a variable or
|
||||
* all exit-points of a function.
|
||||
*
|
||||
* Used in cases such as split view, notebooks, etc. where there can be multiple documents
|
||||
* with shared symbols.
|
||||
*/
|
||||
provideMultiDocumentHighlights(primaryModel: model.ITextModel, position: Position, otherModels: model.ITextModel[], token: CancellationToken): ProviderResult<Map<URI, DocumentHighlight[]>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The linked editing range provider interface defines the contract between extensions and
|
||||
* the linked editing feature.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const ILanguageFeaturesService = createDecorator<ILanguageFeaturesService>('ILanguageFeaturesService');
|
||||
@@ -49,6 +49,8 @@ export interface ILanguageFeaturesService {
|
||||
|
||||
readonly documentHighlightProvider: LanguageFeatureRegistry<DocumentHighlightProvider>;
|
||||
|
||||
readonly multiDocumentHighlightProvider: LanguageFeatureRegistry<MultiDocumentHighlightProvider>;
|
||||
|
||||
readonly documentRangeSemanticTokensProvider: LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>;
|
||||
|
||||
readonly documentSemanticTokensProvider: LanguageFeatureRegistry<DocumentSemanticTokensProvider>;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages';
|
||||
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
@@ -30,6 +30,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService {
|
||||
readonly signatureHelpProvider = new LanguageFeatureRegistry<SignatureHelpProvider>(this._score.bind(this));
|
||||
readonly hoverProvider = new LanguageFeatureRegistry<HoverProvider>(this._score.bind(this));
|
||||
readonly documentHighlightProvider = new LanguageFeatureRegistry<DocumentHighlightProvider>(this._score.bind(this));
|
||||
readonly multiDocumentHighlightProvider = new LanguageFeatureRegistry<MultiDocumentHighlightProvider>(this._score.bind(this));
|
||||
readonly selectionRangeProvider = new LanguageFeatureRegistry<SelectionRangeProvider>(this._score.bind(this));
|
||||
readonly foldingRangeProvider = new LanguageFeatureRegistry<FoldingRangeProvider>(this._score.bind(this));
|
||||
readonly linkProvider = new LanguageFeatureRegistry<LinkProvider>(this._score.bind(this));
|
||||
|
||||
@@ -253,74 +253,75 @@ export enum EditorOption {
|
||||
multiCursorModifier = 77,
|
||||
multiCursorPaste = 78,
|
||||
multiCursorLimit = 79,
|
||||
occurrencesHighlight = 80,
|
||||
overviewRulerBorder = 81,
|
||||
overviewRulerLanes = 82,
|
||||
padding = 83,
|
||||
pasteAs = 84,
|
||||
parameterHints = 85,
|
||||
peekWidgetDefaultFocus = 86,
|
||||
definitionLinkOpensInPeek = 87,
|
||||
quickSuggestions = 88,
|
||||
quickSuggestionsDelay = 89,
|
||||
readOnly = 90,
|
||||
readOnlyMessage = 91,
|
||||
renameOnType = 92,
|
||||
renderControlCharacters = 93,
|
||||
renderFinalNewline = 94,
|
||||
renderLineHighlight = 95,
|
||||
renderLineHighlightOnlyWhenFocus = 96,
|
||||
renderValidationDecorations = 97,
|
||||
renderWhitespace = 98,
|
||||
revealHorizontalRightPadding = 99,
|
||||
roundedSelection = 100,
|
||||
rulers = 101,
|
||||
scrollbar = 102,
|
||||
scrollBeyondLastColumn = 103,
|
||||
scrollBeyondLastLine = 104,
|
||||
scrollPredominantAxis = 105,
|
||||
selectionClipboard = 106,
|
||||
selectionHighlight = 107,
|
||||
selectOnLineNumbers = 108,
|
||||
showFoldingControls = 109,
|
||||
showUnused = 110,
|
||||
snippetSuggestions = 111,
|
||||
smartSelect = 112,
|
||||
smoothScrolling = 113,
|
||||
stickyScroll = 114,
|
||||
stickyTabStops = 115,
|
||||
stopRenderingLineAfter = 116,
|
||||
suggest = 117,
|
||||
suggestFontSize = 118,
|
||||
suggestLineHeight = 119,
|
||||
suggestOnTriggerCharacters = 120,
|
||||
suggestSelection = 121,
|
||||
tabCompletion = 122,
|
||||
tabIndex = 123,
|
||||
unicodeHighlighting = 124,
|
||||
unusualLineTerminators = 125,
|
||||
useShadowDOM = 126,
|
||||
useTabStops = 127,
|
||||
wordBreak = 128,
|
||||
wordSeparators = 129,
|
||||
wordWrap = 130,
|
||||
wordWrapBreakAfterCharacters = 131,
|
||||
wordWrapBreakBeforeCharacters = 132,
|
||||
wordWrapColumn = 133,
|
||||
wordWrapOverride1 = 134,
|
||||
wordWrapOverride2 = 135,
|
||||
wrappingIndent = 136,
|
||||
wrappingStrategy = 137,
|
||||
showDeprecated = 138,
|
||||
inlayHints = 139,
|
||||
editorClassName = 140,
|
||||
pixelRatio = 141,
|
||||
tabFocusMode = 142,
|
||||
layoutInfo = 143,
|
||||
wrappingInfo = 144,
|
||||
defaultColorDecorators = 145,
|
||||
colorDecoratorsActivatedOn = 146,
|
||||
inlineCompletionsAccessibilityVerbose = 147
|
||||
multiDocumentOccurrencesHighlight = 80,
|
||||
occurrencesHighlight = 81,
|
||||
overviewRulerBorder = 82,
|
||||
overviewRulerLanes = 83,
|
||||
padding = 84,
|
||||
pasteAs = 85,
|
||||
parameterHints = 86,
|
||||
peekWidgetDefaultFocus = 87,
|
||||
definitionLinkOpensInPeek = 88,
|
||||
quickSuggestions = 89,
|
||||
quickSuggestionsDelay = 90,
|
||||
readOnly = 91,
|
||||
readOnlyMessage = 92,
|
||||
renameOnType = 93,
|
||||
renderControlCharacters = 94,
|
||||
renderFinalNewline = 95,
|
||||
renderLineHighlight = 96,
|
||||
renderLineHighlightOnlyWhenFocus = 97,
|
||||
renderValidationDecorations = 98,
|
||||
renderWhitespace = 99,
|
||||
revealHorizontalRightPadding = 100,
|
||||
roundedSelection = 101,
|
||||
rulers = 102,
|
||||
scrollbar = 103,
|
||||
scrollBeyondLastColumn = 104,
|
||||
scrollBeyondLastLine = 105,
|
||||
scrollPredominantAxis = 106,
|
||||
selectionClipboard = 107,
|
||||
selectionHighlight = 108,
|
||||
selectOnLineNumbers = 109,
|
||||
showFoldingControls = 110,
|
||||
showUnused = 111,
|
||||
snippetSuggestions = 112,
|
||||
smartSelect = 113,
|
||||
smoothScrolling = 114,
|
||||
stickyScroll = 115,
|
||||
stickyTabStops = 116,
|
||||
stopRenderingLineAfter = 117,
|
||||
suggest = 118,
|
||||
suggestFontSize = 119,
|
||||
suggestLineHeight = 120,
|
||||
suggestOnTriggerCharacters = 121,
|
||||
suggestSelection = 122,
|
||||
tabCompletion = 123,
|
||||
tabIndex = 124,
|
||||
unicodeHighlighting = 125,
|
||||
unusualLineTerminators = 126,
|
||||
useShadowDOM = 127,
|
||||
useTabStops = 128,
|
||||
wordBreak = 129,
|
||||
wordSeparators = 130,
|
||||
wordWrap = 131,
|
||||
wordWrapBreakAfterCharacters = 132,
|
||||
wordWrapBreakBeforeCharacters = 133,
|
||||
wordWrapColumn = 134,
|
||||
wordWrapOverride1 = 135,
|
||||
wordWrapOverride2 = 136,
|
||||
wrappingIndent = 137,
|
||||
wrappingStrategy = 138,
|
||||
showDeprecated = 139,
|
||||
inlayHints = 140,
|
||||
editorClassName = 141,
|
||||
pixelRatio = 142,
|
||||
tabFocusMode = 143,
|
||||
layoutInfo = 144,
|
||||
wrappingInfo = 145,
|
||||
defaultColorDecorators = 146,
|
||||
colorDecoratorsActivatedOn = 147,
|
||||
inlineCompletionsAccessibilityVerbose = 148
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,38 +3,39 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
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, first, timeout } 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';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, ICodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, EditorContributionInstantiation, IActionOptions, registerEditorAction, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
|
||||
import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
|
||||
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 { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider } from 'vs/editor/common/languages';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
|
||||
import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
|
||||
const ctxHasWordHighlights = new RawContextKey<boolean>('hasWordHighlights', false);
|
||||
|
||||
export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry<DocumentHighlightProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<DocumentHighlight[] | null | undefined> {
|
||||
|
||||
export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry<DocumentHighlightProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<ResourceMap<DocumentHighlight[]> | null | undefined> {
|
||||
const orderedByScore = registry.ordered(model);
|
||||
|
||||
// in order of score ask the occurrences provider
|
||||
@@ -43,19 +44,44 @@ export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry<Docum
|
||||
return first<DocumentHighlight[] | null | undefined>(orderedByScore.map(provider => () => {
|
||||
return Promise.resolve(provider.provideDocumentHighlights(model, position, token))
|
||||
.then(undefined, onUnexpectedExternalError);
|
||||
}), arrays.isNonEmptyArray);
|
||||
}), arrays.isNonEmptyArray).then(result => {
|
||||
if (result) {
|
||||
const map = new ResourceMap<DocumentHighlight[]>();
|
||||
map.set(model.uri, result);
|
||||
return map;
|
||||
}
|
||||
return new ResourceMap<DocumentHighlight[]>();
|
||||
});
|
||||
}
|
||||
|
||||
export function getOccurrencesAcrossMultipleModels(registry: LanguageFeatureRegistry<MultiDocumentHighlightProvider>, model: ITextModel, position: Position, wordSeparators: string, token: CancellationToken, otherModels: ITextModel[]): Promise<ResourceMap<DocumentHighlight[]> | null | undefined> {
|
||||
const orderedByScore = registry.ordered(model);
|
||||
|
||||
// in order of score ask the occurrences provider
|
||||
// until someone response with a good result
|
||||
// (good = none empty array)
|
||||
return first<ResourceMap<DocumentHighlight[]> | null | undefined>(orderedByScore.map(provider => () => {
|
||||
return Promise.resolve(provider.provideMultiDocumentHighlights(model, position, otherModels, token))
|
||||
.then(undefined, onUnexpectedExternalError);
|
||||
}), (t: ResourceMap<DocumentHighlight[]> | null | undefined): t is ResourceMap<DocumentHighlight[]> => t instanceof Map && t.size > 0);
|
||||
}
|
||||
|
||||
interface IOccurenceAtPositionRequest {
|
||||
readonly result: Promise<DocumentHighlight[]>;
|
||||
readonly result: Promise<ResourceMap<DocumentHighlight[]>>;
|
||||
isValid(model: ITextModel, selection: Selection, decorations: IEditorDecorationsCollection): boolean;
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
interface IWordHighlighterQuery {
|
||||
readonly model: ITextModel;
|
||||
readonly selection: Selection;
|
||||
readonly word: IWordAtPosition;
|
||||
}
|
||||
|
||||
abstract class OccurenceAtPositionRequest implements IOccurenceAtPositionRequest {
|
||||
|
||||
private readonly _wordRange: Range | null;
|
||||
private _result: CancelablePromise<DocumentHighlight[]> | null;
|
||||
private _result: CancelablePromise<ResourceMap<DocumentHighlight[]>> | null;
|
||||
|
||||
constructor(private readonly _model: ITextModel, private readonly _selection: Selection, private readonly _wordSeparators: string) {
|
||||
this._wordRange = this._getCurrentWordRange(_model, _selection);
|
||||
@@ -70,7 +96,7 @@ abstract class OccurenceAtPositionRequest implements IOccurenceAtPositionRequest
|
||||
|
||||
}
|
||||
|
||||
protected abstract _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<DocumentHighlight[]>;
|
||||
protected abstract _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<ResourceMap<DocumentHighlight[]>>;
|
||||
|
||||
private _getCurrentWordRange(model: ITextModel, selection: Selection): Range | null {
|
||||
const word = model.getWordAtPosition(selection.getPosition());
|
||||
@@ -117,38 +143,73 @@ class SemanticOccurenceAtPositionRequest extends OccurenceAtPositionRequest {
|
||||
this._providers = providers;
|
||||
}
|
||||
|
||||
protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<DocumentHighlight[]> {
|
||||
return getOccurrencesAtPosition(this._providers, model, selection.getPosition(), token).then(value => value || []);
|
||||
protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<ResourceMap<DocumentHighlight[]>> {
|
||||
return getOccurrencesAtPosition(this._providers, model, selection.getPosition(), token).then(value => {
|
||||
if (!value) {
|
||||
return new ResourceMap<DocumentHighlight[]>();
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TextualOccurenceAtPositionRequest extends OccurenceAtPositionRequest {
|
||||
class MultiModelOccurenceRequest extends OccurenceAtPositionRequest {
|
||||
private readonly _providers: LanguageFeatureRegistry<MultiDocumentHighlightProvider>;
|
||||
private readonly _otherModels: ITextModel[];
|
||||
|
||||
constructor(model: ITextModel, selection: Selection, wordSeparators: string, providers: LanguageFeatureRegistry<MultiDocumentHighlightProvider>, otherModels: ITextModel[]) {
|
||||
super(model, selection, wordSeparators);
|
||||
this._providers = providers;
|
||||
this._otherModels = otherModels;
|
||||
}
|
||||
|
||||
protected override _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<ResourceMap<DocumentHighlight[]>> {
|
||||
return getOccurrencesAcrossMultipleModels(this._providers, model, selection.getPosition(), wordSeparators, token, this._otherModels).then(value => {
|
||||
if (!value) {
|
||||
return new ResourceMap<DocumentHighlight[]>();
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TextualOccurenceRequest extends OccurenceAtPositionRequest {
|
||||
|
||||
private readonly _otherModels: ITextModel[];
|
||||
private readonly _selectionIsEmpty: boolean;
|
||||
|
||||
constructor(model: ITextModel, selection: Selection, wordSeparators: string) {
|
||||
constructor(model: ITextModel, selection: Selection, wordSeparators: string, otherModels: ITextModel[]) {
|
||||
super(model, selection, wordSeparators);
|
||||
this._otherModels = otherModels;
|
||||
this._selectionIsEmpty = selection.isEmpty();
|
||||
}
|
||||
|
||||
protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<DocumentHighlight[]> {
|
||||
protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise<ResourceMap<DocumentHighlight[]>> {
|
||||
return timeout(250, token).then(() => {
|
||||
if (!selection.isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = new ResourceMap<DocumentHighlight[]>();
|
||||
const word = model.getWordAtPosition(selection.getPosition());
|
||||
|
||||
if (!word || word.word.length > 1000) {
|
||||
return [];
|
||||
if (!word) {
|
||||
return new ResourceMap<DocumentHighlight[]>();
|
||||
}
|
||||
const matches = model.findMatches(word.word, true, false, true, wordSeparators, false);
|
||||
return matches.map(m => {
|
||||
return {
|
||||
|
||||
const allModels = [model, ...this._otherModels];
|
||||
|
||||
for (const otherModel of allModels) {
|
||||
if (otherModel.isDisposed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const matches = otherModel.findMatches(word.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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,7 +226,14 @@ function computeOccurencesAtPosition(registry: LanguageFeatureRegistry<DocumentH
|
||||
if (registry.has(model)) {
|
||||
return new SemanticOccurenceAtPositionRequest(model, selection, wordSeparators, registry);
|
||||
}
|
||||
return new TextualOccurenceAtPositionRequest(model, selection, wordSeparators);
|
||||
return new TextualOccurenceRequest(model, selection, wordSeparators, []);
|
||||
}
|
||||
|
||||
function computeOccurencesMultiModel(registry: LanguageFeatureRegistry<MultiDocumentHighlightProvider>, model: ITextModel, selection: Selection, wordSeparators: string, otherModels: ITextModel[]): IOccurenceAtPositionRequest {
|
||||
if (registry.has(model)) {
|
||||
return new MultiModelOccurenceRequest(model, selection, wordSeparators, registry, otherModels);
|
||||
}
|
||||
return new TextualOccurenceRequest(model, selection, wordSeparators, otherModels);
|
||||
}
|
||||
|
||||
registerModelAndPositionCommand('_executeDocumentHighlights', (accessor, model, position) => {
|
||||
@@ -177,15 +245,18 @@ class WordHighlighter {
|
||||
|
||||
private readonly editor: IActiveCodeEditor;
|
||||
private readonly providers: LanguageFeatureRegistry<DocumentHighlightProvider>;
|
||||
private readonly multiDocumentProviders: LanguageFeatureRegistry<MultiDocumentHighlightProvider>;
|
||||
private occurrencesHighlight: boolean;
|
||||
private multiDocumentOccurrencesHighlight: boolean;
|
||||
private readonly model: ITextModel;
|
||||
private readonly decorations: IEditorDecorationsCollection;
|
||||
private readonly toUnhook = new DisposableStore();
|
||||
private readonly codeEditorService: ICodeEditorService;
|
||||
|
||||
private workerRequestTokenId: number = 0;
|
||||
private workerRequest: IOccurenceAtPositionRequest | null;
|
||||
private workerRequestCompleted: boolean = false;
|
||||
private workerRequestValue: DocumentHighlight[] = [];
|
||||
private workerRequestValue: ResourceMap<DocumentHighlight[]> = new ResourceMap();
|
||||
|
||||
private lastCursorPositionChangeTime: number = 0;
|
||||
private renderDecorationsTimer: any = -1;
|
||||
@@ -193,18 +264,20 @@ class WordHighlighter {
|
||||
private readonly _hasWordHighlights: IContextKey<boolean>;
|
||||
private _ignorePositionChangeEvent: boolean;
|
||||
|
||||
private readonly linkedHighlighters: () => Iterable<WordHighlighter | null>;
|
||||
private static storedDecorations: ResourceMap<string[]> = new ResourceMap();
|
||||
private static query: IWordHighlighterQuery | null = null;
|
||||
|
||||
constructor(editor: IActiveCodeEditor, providers: LanguageFeatureRegistry<DocumentHighlightProvider>, linkedHighlighters: () => Iterable<WordHighlighter | null>, contextKeyService: IContextKeyService) {
|
||||
constructor(editor: IActiveCodeEditor, providers: LanguageFeatureRegistry<DocumentHighlightProvider>, multiProviders: LanguageFeatureRegistry<MultiDocumentHighlightProvider>, contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService) {
|
||||
this.editor = editor;
|
||||
this.providers = providers;
|
||||
this.linkedHighlighters = linkedHighlighters;
|
||||
this.multiDocumentProviders = multiProviders;
|
||||
this.codeEditorService = codeEditorService;
|
||||
this._hasWordHighlights = ctxHasWordHighlights.bindTo(contextKeyService);
|
||||
this._ignorePositionChangeEvent = false;
|
||||
this.occurrencesHighlight = this.editor.getOption(EditorOption.occurrencesHighlight);
|
||||
this.multiDocumentOccurrencesHighlight = this.editor.getOption(EditorOption.multiDocumentOccurrencesHighlight);
|
||||
this.model = this.editor.getModel();
|
||||
this.toUnhook.add(editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
|
||||
if (this._ignorePositionChangeEvent) {
|
||||
// We are changing the position => ignore this event
|
||||
return;
|
||||
@@ -221,12 +294,27 @@ class WordHighlighter {
|
||||
this.toUnhook.add(editor.onDidChangeModelContent((e) => {
|
||||
this._stopAll();
|
||||
}));
|
||||
this.toUnhook.add(editor.onDidChangeModel((e) => {
|
||||
if (!e.newModelUrl && e.oldModelUrl) {
|
||||
this._stopSingular();
|
||||
} else {
|
||||
if (WordHighlighter.query) {
|
||||
this._run();
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.toUnhook.add(editor.onDidChangeConfiguration((e) => {
|
||||
const newValue = this.editor.getOption(EditorOption.occurrencesHighlight);
|
||||
if (this.occurrencesHighlight !== newValue) {
|
||||
this.occurrencesHighlight = newValue;
|
||||
this._stopAll();
|
||||
}
|
||||
|
||||
const newMultiDocumentValue = this.editor.getOption(EditorOption.multiDocumentOccurrencesHighlight);
|
||||
if (this.multiDocumentOccurrencesHighlight !== newMultiDocumentValue) {
|
||||
this.multiDocumentOccurrencesHighlight = newMultiDocumentValue;
|
||||
this._stopAll();
|
||||
}
|
||||
}));
|
||||
|
||||
this.decorations = this.editor.createDecorationsCollection();
|
||||
@@ -236,6 +324,11 @@ class WordHighlighter {
|
||||
|
||||
this.lastCursorPositionChangeTime = 0;
|
||||
this.renderDecorationsTimer = -1;
|
||||
|
||||
// if there is a query already, highlight off that query
|
||||
// if (WordHighlighter.query) {
|
||||
// this._run();
|
||||
// }
|
||||
}
|
||||
|
||||
public hasDecorations(): boolean {
|
||||
@@ -302,17 +395,93 @@ class WordHighlighter {
|
||||
}
|
||||
}
|
||||
|
||||
private _removeDecorations(): void {
|
||||
private _removeSingleDecorations(): void {
|
||||
// Clear current query if editor is focused
|
||||
// if (this.editor.hasWidgetFocus() && this.editor.getModel()?.uri.scheme !== Schemas.vscodeNotebookCell) {
|
||||
if (this.editor.hasWidgetFocus() && !(WordHighlighter.query?.model.uri.scheme === Schemas.vscodeNotebookCell || this.editor.getModel()?.uri.scheme === Schemas.vscodeNotebookCell)) {
|
||||
// if (this.editor.hasWidgetFocus()) {
|
||||
// ! WordHighlighter.query?.model.dispose();
|
||||
WordHighlighter.query = null;
|
||||
}
|
||||
|
||||
// return if no model
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDecorationIDs = WordHighlighter.storedDecorations.get(this.editor.getModel().uri);
|
||||
if (!currentDecorationIDs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editor.removeDecorations(currentDecorationIDs);
|
||||
WordHighlighter.storedDecorations.delete(this.editor.getModel().uri);
|
||||
|
||||
if (this.decorations.length > 0) {
|
||||
// remove decorations
|
||||
this.decorations.clear();
|
||||
this._hasWordHighlights.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private _removeAllDecorations(): void {
|
||||
const currentEditors = this.codeEditorService.listCodeEditors();
|
||||
// iterate over editors and store models in currentModels
|
||||
for (const editor of currentEditors) {
|
||||
if (!editor.hasModel()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentDecorationIDs = WordHighlighter.storedDecorations.get(editor.getModel().uri);
|
||||
if (!currentDecorationIDs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
editor.removeDecorations(currentDecorationIDs);
|
||||
WordHighlighter.storedDecorations.delete(editor.getModel().uri);
|
||||
|
||||
const editorHighlighterContrib = WordHighlighterContribution.get(editor);
|
||||
if (!editorHighlighterContrib?.wordHighlighter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (editorHighlighterContrib.wordHighlighter.decorations.length > 0) {
|
||||
editorHighlighterContrib.wordHighlighter.decorations.clear();
|
||||
editorHighlighterContrib.wordHighlighter._hasWordHighlights.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _stopSingular(): void {
|
||||
// Remove any existing decorations + a possible query, and re - run to update decorations
|
||||
this._removeSingleDecorations();
|
||||
|
||||
// don't re-run notebooks
|
||||
if (this.editor.getModel()?.uri.scheme !== Schemas.vscodeNotebookCell) {
|
||||
this._run();
|
||||
}
|
||||
|
||||
// Cancel any renderDecorationsTimer
|
||||
if (this.renderDecorationsTimer !== -1) {
|
||||
clearTimeout(this.renderDecorationsTimer);
|
||||
this.renderDecorationsTimer = -1;
|
||||
}
|
||||
|
||||
// Cancel any worker request
|
||||
if (this.workerRequest !== null) {
|
||||
this.workerRequest.cancel();
|
||||
this.workerRequest = null;
|
||||
}
|
||||
|
||||
// Invalidate any worker request callback
|
||||
if (!this.workerRequestCompleted) {
|
||||
this.workerRequestTokenId++;
|
||||
this.workerRequestCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _stopAll(): void {
|
||||
// Remove any existing decorations
|
||||
this._removeDecorations();
|
||||
this._removeAllDecorations();
|
||||
|
||||
// Cancel any renderDecorationsTimer
|
||||
if (this.renderDecorationsTimer !== -1) {
|
||||
@@ -342,7 +511,8 @@ class WordHighlighter {
|
||||
}
|
||||
|
||||
// ignore typing & other
|
||||
if (e.reason !== CursorChangeReason.Explicit) {
|
||||
// need to check if the model is a notebook cell, should not stop if nb
|
||||
if (e.reason !== CursorChangeReason.Explicit && this.editor.getModel()?.uri.scheme !== Schemas.vscodeNotebookCell) {
|
||||
this._stopAll();
|
||||
return;
|
||||
}
|
||||
@@ -361,33 +531,106 @@ class WordHighlighter {
|
||||
});
|
||||
}
|
||||
|
||||
private getOtherModelsToHighlight(model: ITextModel): ITextModel[] {
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// notebook case
|
||||
const isNotebookEditor = model.uri.scheme === Schemas.vscodeNotebookCell;
|
||||
if (isNotebookEditor) {
|
||||
const currentModels: ITextModel[] = [];
|
||||
const currentEditors = this.codeEditorService.listCodeEditors();
|
||||
for (const editor of currentEditors) {
|
||||
const tempModel = editor.getModel();
|
||||
if (tempModel && tempModel !== model && tempModel.uri.scheme === Schemas.vscodeNotebookCell) {
|
||||
currentModels.push(tempModel);
|
||||
}
|
||||
}
|
||||
return currentModels;
|
||||
}
|
||||
|
||||
// inline case
|
||||
// ? current works when highlighting outside of an inline diff, highlighting in.
|
||||
// ? broken when highlighting within a diff editor. highlighting the main editor does not work
|
||||
// ? editor group service could be useful here
|
||||
const currentModels: ITextModel[] = [];
|
||||
const currentEditors = this.codeEditorService.listCodeEditors();
|
||||
for (const editor of currentEditors) {
|
||||
if (!isDiffEditor(editor)) {
|
||||
continue;
|
||||
}
|
||||
const diffModel = (editor as IDiffEditor).getModel();
|
||||
if (!diffModel) {
|
||||
continue;
|
||||
}
|
||||
if (model === diffModel.modified) { // embedded inline chat diff would pass this, allowing highlights
|
||||
//? currentModels.push(diffModel.original);
|
||||
currentModels.push(diffModel.modified);
|
||||
}
|
||||
}
|
||||
if (currentModels.length) { // no matching editors have been found
|
||||
return currentModels;
|
||||
}
|
||||
|
||||
// multi-doc OFF
|
||||
if (!this.multiDocumentOccurrencesHighlight) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// multi-doc ON
|
||||
for (const editor of currentEditors) {
|
||||
const tempModel = editor.getModel();
|
||||
if (tempModel && tempModel !== model) {
|
||||
currentModels.push(tempModel);
|
||||
}
|
||||
}
|
||||
return currentModels;
|
||||
}
|
||||
|
||||
private _run(): void {
|
||||
const editorSelection = this.editor.getSelection();
|
||||
|
||||
// ignore multiline selection
|
||||
if (editorSelection.startLineNumber !== editorSelection.endLineNumber) {
|
||||
this._stopAll();
|
||||
return;
|
||||
let workerRequestIsValid;
|
||||
if (!this.editor.hasWidgetFocus()) { // no focus (new nb cell, etc)
|
||||
if (WordHighlighter.query === null) {
|
||||
// no previous query, nothing to highlight
|
||||
this._stopAll();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const editorSelection = this.editor.getSelection();
|
||||
|
||||
// ignore multiline selection
|
||||
if (!editorSelection || editorSelection.startLineNumber !== editorSelection.endLineNumber) {
|
||||
this._stopAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const startColumn = editorSelection.startColumn;
|
||||
const endColumn = editorSelection.endColumn;
|
||||
|
||||
const word = this._getWord();
|
||||
|
||||
// The selection must be inside a word or surround one word at most
|
||||
if (!word || word.startColumn > startColumn || word.endColumn < endColumn) {
|
||||
// no previous query, nothing to highlight
|
||||
this._stopAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// All the effort below is trying to achieve this:
|
||||
// - when cursor is moved to a word, trigger immediately a findOccurrences request
|
||||
// - 250ms later after the last cursor move event, render the occurrences
|
||||
// - no flickering!
|
||||
workerRequestIsValid = (this.workerRequest && this.workerRequest.isValid(this.model, editorSelection, this.decorations));
|
||||
|
||||
WordHighlighter.query = {
|
||||
model: this.model,
|
||||
selection: editorSelection,
|
||||
word: word
|
||||
};
|
||||
}
|
||||
|
||||
const startColumn = editorSelection.startColumn;
|
||||
const endColumn = editorSelection.endColumn;
|
||||
|
||||
const word = this._getWord();
|
||||
|
||||
// The selection must be inside a word or surround one word at most
|
||||
if (!word || word.startColumn > startColumn || word.endColumn < endColumn) {
|
||||
this._stopAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// All the effort below is trying to achieve this:
|
||||
// - when cursor is moved to a word, trigger immediately a findOccurrences request
|
||||
// - 250ms later after the last cursor move event, render the occurrences
|
||||
// - no flickering!
|
||||
|
||||
const workerRequestIsValid = (this.workerRequest && this.workerRequest.isValid(this.model, editorSelection, this.decorations));
|
||||
|
||||
// There are 4 cases:
|
||||
// a) old workerRequest is valid & completed, renderDecorationsTimer fired
|
||||
// b) old workerRequest is valid & completed, renderDecorationsTimer not fired
|
||||
@@ -415,7 +658,13 @@ class WordHighlighter {
|
||||
const myRequestId = ++this.workerRequestTokenId;
|
||||
this.workerRequestCompleted = false;
|
||||
|
||||
this.workerRequest = computeOccurencesAtPosition(this.providers, this.model, this.editor.getSelection(), this.editor.getOption(EditorOption.wordSeparators));
|
||||
const otherModelsToHighlight = this.getOtherModelsToHighlight(this.editor.getModel());
|
||||
|
||||
if (!otherModelsToHighlight.length) {
|
||||
this.workerRequest = computeOccurencesAtPosition(this.providers, WordHighlighter.query.model, WordHighlighter.query.selection, this.editor.getOption(EditorOption.wordSeparators));
|
||||
} else {
|
||||
this.workerRequest = computeOccurencesMultiModel(this.multiDocumentProviders, WordHighlighter.query.model, WordHighlighter.query.selection, this.editor.getOption(EditorOption.wordSeparators), otherModelsToHighlight);
|
||||
}
|
||||
|
||||
this.workerRequest.result.then(data => {
|
||||
if (myRequestId === this.workerRequestTokenId) {
|
||||
@@ -445,31 +694,45 @@ class WordHighlighter {
|
||||
|
||||
private renderDecorations(): void {
|
||||
this.renderDecorationsTimer = -1;
|
||||
const decorations: IModelDeltaDecoration[] = [];
|
||||
for (const info of this.workerRequestValue) {
|
||||
if (info.range) {
|
||||
decorations.push({
|
||||
range: info.range,
|
||||
options: getHighlightDecorationOptions(info.kind)
|
||||
});
|
||||
// create new loop, iterate over current editors using this.codeEditorService.listCodeEditors(),
|
||||
// if the URI of that codeEditor is in the map, then add the decorations to the decorations array
|
||||
// then set the decorations for the editor
|
||||
const currentEditors = this.codeEditorService.listCodeEditors();
|
||||
for (const editor of currentEditors) {
|
||||
const editorHighlighterContrib = WordHighlighterContribution.get(editor);
|
||||
if (!editorHighlighterContrib) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this.decorations.set(decorations);
|
||||
this._hasWordHighlights.set(this.hasDecorations());
|
||||
const newDecorations: IModelDeltaDecoration[] = [];
|
||||
const uri = editor.getModel()?.uri;
|
||||
if (uri && this.workerRequestValue.has(uri)) {
|
||||
const oldDecorationIDs: string[] | undefined = WordHighlighter.storedDecorations.get(uri);
|
||||
const newDocumentHighlights = this.workerRequestValue.get(uri);
|
||||
if (newDocumentHighlights) {
|
||||
for (const highlight of newDocumentHighlights) {
|
||||
newDecorations.push({
|
||||
range: highlight.range,
|
||||
options: getHighlightDecorationOptions(highlight.kind)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// update decorators of friends
|
||||
for (const other of this.linkedHighlighters()) {
|
||||
if (other?.editor.getModel() === this.editor.getModel()) {
|
||||
other._stopAll();
|
||||
other.decorations.set(decorations);
|
||||
other._hasWordHighlights.set(other.hasDecorations());
|
||||
let newDecorationIDs: string[] = [];
|
||||
editor.changeDecorations((changeAccessor) => {
|
||||
newDecorationIDs = changeAccessor.deltaDecorations(oldDecorationIDs ?? [], newDecorations);
|
||||
});
|
||||
WordHighlighter.storedDecorations = WordHighlighter.storedDecorations.set(uri, newDecorationIDs);
|
||||
|
||||
if (newDecorations.length > 0) {
|
||||
editorHighlighterContrib.wordHighlighter?._hasWordHighlights.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._stopAll();
|
||||
this._stopSingular();
|
||||
this.toUnhook.dispose();
|
||||
}
|
||||
}
|
||||
@@ -482,70 +745,59 @@ export class WordHighlighterContribution extends Disposable implements IEditorCo
|
||||
return editor.getContribution<WordHighlighterContribution>(WordHighlighterContribution.ID);
|
||||
}
|
||||
|
||||
private wordHighlighter: WordHighlighter | null;
|
||||
private linkedContributions: Set<WordHighlighterContribution>;
|
||||
private _wordHighlighter: WordHighlighter | null;
|
||||
|
||||
constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService) {
|
||||
constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @ICodeEditorService codeEditorService: ICodeEditorService) {
|
||||
super();
|
||||
this.wordHighlighter = null;
|
||||
this.linkedContributions = new Set();
|
||||
this._wordHighlighter = null;
|
||||
const createWordHighlighterIfPossible = () => {
|
||||
if (editor.hasModel() && !editor.getModel().isTooLargeForTokenization()) {
|
||||
this.wordHighlighter = new WordHighlighter(editor, languageFeaturesService.documentHighlightProvider, () => Iterable.map(this.linkedContributions, c => c.wordHighlighter), contextKeyService);
|
||||
this._wordHighlighter = new WordHighlighter(editor, languageFeaturesService.documentHighlightProvider, languageFeaturesService.multiDocumentHighlightProvider, contextKeyService, codeEditorService);
|
||||
}
|
||||
};
|
||||
this._register(editor.onDidChangeModel((e) => {
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.dispose();
|
||||
this.wordHighlighter = null;
|
||||
if (this._wordHighlighter) {
|
||||
this._wordHighlighter.dispose();
|
||||
this._wordHighlighter = null;
|
||||
}
|
||||
createWordHighlighterIfPossible();
|
||||
}));
|
||||
createWordHighlighterIfPossible();
|
||||
}
|
||||
|
||||
public get wordHighlighter(): WordHighlighter | null {
|
||||
return this._wordHighlighter;
|
||||
}
|
||||
|
||||
public saveViewState(): boolean {
|
||||
if (this.wordHighlighter && this.wordHighlighter.hasDecorations()) {
|
||||
if (this._wordHighlighter && this._wordHighlighter.hasDecorations()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public moveNext() {
|
||||
this.wordHighlighter?.moveNext();
|
||||
this._wordHighlighter?.moveNext();
|
||||
}
|
||||
|
||||
public moveBack() {
|
||||
this.wordHighlighter?.moveBack();
|
||||
this._wordHighlighter?.moveBack();
|
||||
}
|
||||
|
||||
public restoreViewState(state: boolean | undefined): void {
|
||||
if (this.wordHighlighter && state) {
|
||||
this.wordHighlighter.restore();
|
||||
if (this._wordHighlighter && state) {
|
||||
this._wordHighlighter.restore();
|
||||
}
|
||||
}
|
||||
|
||||
public stopHighlighting() {
|
||||
this.wordHighlighter?.stop();
|
||||
}
|
||||
|
||||
public linkWordHighlighters(editor: ICodeEditor): IDisposable {
|
||||
const other = WordHighlighterContribution.get(editor);
|
||||
if (!other) {
|
||||
return Disposable.None;
|
||||
}
|
||||
this.linkedContributions.add(other);
|
||||
other.linkedContributions.add(this);
|
||||
return toDisposable(() => {
|
||||
this.linkedContributions.delete(other);
|
||||
other.linkedContributions.delete(this);
|
||||
});
|
||||
this._wordHighlighter?.stop();
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.dispose();
|
||||
this.wordHighlighter = null;
|
||||
if (this._wordHighlighter) {
|
||||
this._wordHighlighter.dispose();
|
||||
this._wordHighlighter = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Vendored
+86
-68
@@ -3531,6 +3531,11 @@ declare namespace monaco.editor {
|
||||
* Defaults to true.
|
||||
*/
|
||||
occurrencesHighlight?: boolean;
|
||||
/**
|
||||
* Enable semantic occurrences highlight.
|
||||
* Defaults to true.
|
||||
*/
|
||||
multiDocumentOccurrencesHighlight?: boolean;
|
||||
/**
|
||||
* Show code lens
|
||||
* Defaults to true.
|
||||
@@ -4786,74 +4791,75 @@ declare namespace monaco.editor {
|
||||
multiCursorModifier = 77,
|
||||
multiCursorPaste = 78,
|
||||
multiCursorLimit = 79,
|
||||
occurrencesHighlight = 80,
|
||||
overviewRulerBorder = 81,
|
||||
overviewRulerLanes = 82,
|
||||
padding = 83,
|
||||
pasteAs = 84,
|
||||
parameterHints = 85,
|
||||
peekWidgetDefaultFocus = 86,
|
||||
definitionLinkOpensInPeek = 87,
|
||||
quickSuggestions = 88,
|
||||
quickSuggestionsDelay = 89,
|
||||
readOnly = 90,
|
||||
readOnlyMessage = 91,
|
||||
renameOnType = 92,
|
||||
renderControlCharacters = 93,
|
||||
renderFinalNewline = 94,
|
||||
renderLineHighlight = 95,
|
||||
renderLineHighlightOnlyWhenFocus = 96,
|
||||
renderValidationDecorations = 97,
|
||||
renderWhitespace = 98,
|
||||
revealHorizontalRightPadding = 99,
|
||||
roundedSelection = 100,
|
||||
rulers = 101,
|
||||
scrollbar = 102,
|
||||
scrollBeyondLastColumn = 103,
|
||||
scrollBeyondLastLine = 104,
|
||||
scrollPredominantAxis = 105,
|
||||
selectionClipboard = 106,
|
||||
selectionHighlight = 107,
|
||||
selectOnLineNumbers = 108,
|
||||
showFoldingControls = 109,
|
||||
showUnused = 110,
|
||||
snippetSuggestions = 111,
|
||||
smartSelect = 112,
|
||||
smoothScrolling = 113,
|
||||
stickyScroll = 114,
|
||||
stickyTabStops = 115,
|
||||
stopRenderingLineAfter = 116,
|
||||
suggest = 117,
|
||||
suggestFontSize = 118,
|
||||
suggestLineHeight = 119,
|
||||
suggestOnTriggerCharacters = 120,
|
||||
suggestSelection = 121,
|
||||
tabCompletion = 122,
|
||||
tabIndex = 123,
|
||||
unicodeHighlighting = 124,
|
||||
unusualLineTerminators = 125,
|
||||
useShadowDOM = 126,
|
||||
useTabStops = 127,
|
||||
wordBreak = 128,
|
||||
wordSeparators = 129,
|
||||
wordWrap = 130,
|
||||
wordWrapBreakAfterCharacters = 131,
|
||||
wordWrapBreakBeforeCharacters = 132,
|
||||
wordWrapColumn = 133,
|
||||
wordWrapOverride1 = 134,
|
||||
wordWrapOverride2 = 135,
|
||||
wrappingIndent = 136,
|
||||
wrappingStrategy = 137,
|
||||
showDeprecated = 138,
|
||||
inlayHints = 139,
|
||||
editorClassName = 140,
|
||||
pixelRatio = 141,
|
||||
tabFocusMode = 142,
|
||||
layoutInfo = 143,
|
||||
wrappingInfo = 144,
|
||||
defaultColorDecorators = 145,
|
||||
colorDecoratorsActivatedOn = 146,
|
||||
inlineCompletionsAccessibilityVerbose = 147
|
||||
multiDocumentOccurrencesHighlight = 80,
|
||||
occurrencesHighlight = 81,
|
||||
overviewRulerBorder = 82,
|
||||
overviewRulerLanes = 83,
|
||||
padding = 84,
|
||||
pasteAs = 85,
|
||||
parameterHints = 86,
|
||||
peekWidgetDefaultFocus = 87,
|
||||
definitionLinkOpensInPeek = 88,
|
||||
quickSuggestions = 89,
|
||||
quickSuggestionsDelay = 90,
|
||||
readOnly = 91,
|
||||
readOnlyMessage = 92,
|
||||
renameOnType = 93,
|
||||
renderControlCharacters = 94,
|
||||
renderFinalNewline = 95,
|
||||
renderLineHighlight = 96,
|
||||
renderLineHighlightOnlyWhenFocus = 97,
|
||||
renderValidationDecorations = 98,
|
||||
renderWhitespace = 99,
|
||||
revealHorizontalRightPadding = 100,
|
||||
roundedSelection = 101,
|
||||
rulers = 102,
|
||||
scrollbar = 103,
|
||||
scrollBeyondLastColumn = 104,
|
||||
scrollBeyondLastLine = 105,
|
||||
scrollPredominantAxis = 106,
|
||||
selectionClipboard = 107,
|
||||
selectionHighlight = 108,
|
||||
selectOnLineNumbers = 109,
|
||||
showFoldingControls = 110,
|
||||
showUnused = 111,
|
||||
snippetSuggestions = 112,
|
||||
smartSelect = 113,
|
||||
smoothScrolling = 114,
|
||||
stickyScroll = 115,
|
||||
stickyTabStops = 116,
|
||||
stopRenderingLineAfter = 117,
|
||||
suggest = 118,
|
||||
suggestFontSize = 119,
|
||||
suggestLineHeight = 120,
|
||||
suggestOnTriggerCharacters = 121,
|
||||
suggestSelection = 122,
|
||||
tabCompletion = 123,
|
||||
tabIndex = 124,
|
||||
unicodeHighlighting = 125,
|
||||
unusualLineTerminators = 126,
|
||||
useShadowDOM = 127,
|
||||
useTabStops = 128,
|
||||
wordBreak = 129,
|
||||
wordSeparators = 130,
|
||||
wordWrap = 131,
|
||||
wordWrapBreakAfterCharacters = 132,
|
||||
wordWrapBreakBeforeCharacters = 133,
|
||||
wordWrapColumn = 134,
|
||||
wordWrapOverride1 = 135,
|
||||
wordWrapOverride2 = 136,
|
||||
wrappingIndent = 137,
|
||||
wrappingStrategy = 138,
|
||||
showDeprecated = 139,
|
||||
inlayHints = 140,
|
||||
editorClassName = 141,
|
||||
pixelRatio = 142,
|
||||
tabFocusMode = 143,
|
||||
layoutInfo = 144,
|
||||
wrappingInfo = 145,
|
||||
defaultColorDecorators = 146,
|
||||
colorDecoratorsActivatedOn = 147,
|
||||
inlineCompletionsAccessibilityVerbose = 148
|
||||
}
|
||||
|
||||
export const EditorOptions: {
|
||||
@@ -4940,6 +4946,7 @@ declare namespace monaco.editor {
|
||||
multiCursorPaste: IEditorOption<EditorOption.multiCursorPaste, 'spread' | 'full'>;
|
||||
multiCursorLimit: IEditorOption<EditorOption.multiCursorLimit, number>;
|
||||
occurrencesHighlight: IEditorOption<EditorOption.occurrencesHighlight, boolean>;
|
||||
multiDocumentOccurrencesHighlight: IEditorOption<EditorOption.multiDocumentOccurrencesHighlight, boolean>;
|
||||
overviewRulerBorder: IEditorOption<EditorOption.overviewRulerBorder, boolean>;
|
||||
overviewRulerLanes: IEditorOption<EditorOption.overviewRulerLanes, number>;
|
||||
padding: IEditorOption<EditorOption.padding, Readonly<Required<IEditorPaddingOptions>>>;
|
||||
@@ -7146,6 +7153,17 @@ declare namespace monaco.languages {
|
||||
provideDocumentHighlights(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||
}
|
||||
|
||||
export interface MultiDocumentHighlightProvider {
|
||||
/**
|
||||
* Provide a Map of Uri --> document highlights, like all occurrences of a variable or
|
||||
* all exit-points of a function.
|
||||
*
|
||||
* Used in cases such as split view, notebooks, etc. where there can be multiple documents
|
||||
* with shared symbols.
|
||||
*/
|
||||
provideMultiDocumentHighlights(primaryModel: editor.ITextModel, position: Position, otherModels: editor.ITextModel[], token: CancellationToken): ProviderResult<Map<Uri, DocumentHighlight[]>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The linked editing range provider interface defines the contract between extensions and
|
||||
* the linked editing feature.
|
||||
|
||||
@@ -33,6 +33,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
|
||||
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol';
|
||||
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
|
||||
export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape {
|
||||
@@ -293,11 +294,51 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
|
||||
// --- occurrences
|
||||
|
||||
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {
|
||||
this._registrations.set(handle, this._languageFeaturesService.documentHighlightProvider.register(selector, <languages.DocumentHighlightProvider>{
|
||||
provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.DocumentHighlight[] | undefined> => {
|
||||
return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token);
|
||||
}
|
||||
}));
|
||||
this._registrations.set(handle, combinedDisposable(
|
||||
this._languageFeaturesService.documentHighlightProvider.register(selector, <languages.DocumentHighlightProvider>{
|
||||
provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.DocumentHighlight[] | undefined> => {
|
||||
return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token);
|
||||
}
|
||||
}),
|
||||
this._languageFeaturesService.multiDocumentHighlightProvider.register(selector, <languages.MultiDocumentHighlightProvider>{
|
||||
provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise<Map<URI, languages.DocumentHighlight[]>> => {
|
||||
const res = this._proxy.$provideDocumentHighlights(handle, model.uri, position, token);
|
||||
if (!res) {
|
||||
return Promise.resolve(new Map<URI, languages.DocumentHighlight[]>());
|
||||
}
|
||||
|
||||
return res.then(data => {
|
||||
const result = new Map<URI, languages.DocumentHighlight[]>();
|
||||
if (data) {
|
||||
result.set(model.uri, data);
|
||||
}
|
||||
|
||||
const word = model.getWordAtPosition({
|
||||
lineNumber: position.lineNumber,
|
||||
column: position.column
|
||||
});
|
||||
|
||||
if (!word) {
|
||||
return new Map<URI, languages.DocumentHighlight[]>();
|
||||
}
|
||||
|
||||
for (const otherModel of otherModels) {
|
||||
const matches = otherModel.findMatches(word.word, true, false, true, USUAL_WORD_SEPARATORS, false);
|
||||
const highlights = matches.map(m => ({
|
||||
range: m.range,
|
||||
kind: languages.DocumentHighlightKind.Text
|
||||
}));
|
||||
|
||||
if (highlights) {
|
||||
result.set(otherModel.uri, highlights);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
// --- linked editing
|
||||
|
||||
@@ -508,8 +508,8 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
|
||||
await rpcProtocol.sync();
|
||||
const value = (await getOccurrencesAtPosition(languageFeaturesService.documentHighlightProvider, model, new EditorPosition(1, 2), CancellationToken.None))!;
|
||||
assert.strictEqual(value.length, 1);
|
||||
const [entry] = value;
|
||||
assert.strictEqual(value.size, 1);
|
||||
const [entry] = Array.from(value.values())[0];
|
||||
assert.deepStrictEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 });
|
||||
assert.strictEqual(entry.kind, languages.DocumentHighlightKind.Text);
|
||||
});
|
||||
@@ -529,8 +529,8 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
|
||||
await rpcProtocol.sync();
|
||||
const value = (await getOccurrencesAtPosition(languageFeaturesService.documentHighlightProvider, model, new EditorPosition(1, 2), CancellationToken.None))!;
|
||||
assert.strictEqual(value.length, 1);
|
||||
const [entry] = value;
|
||||
assert.strictEqual(value.size, 1);
|
||||
const [entry] = Array.from(value.values())[0];
|
||||
assert.deepStrictEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 });
|
||||
assert.strictEqual(entry.kind, languages.DocumentHighlightKind.Text);
|
||||
});
|
||||
@@ -550,8 +550,8 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
|
||||
await rpcProtocol.sync();
|
||||
const value = (await getOccurrencesAtPosition(languageFeaturesService.documentHighlightProvider, model, new EditorPosition(1, 2), CancellationToken.None))!;
|
||||
assert.strictEqual(value.length, 1);
|
||||
const [entry] = value;
|
||||
assert.strictEqual(value.size, 1);
|
||||
const [entry] = Array.from(value.values())[0];
|
||||
assert.deepStrictEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 3 });
|
||||
assert.strictEqual(entry.kind, languages.DocumentHighlightKind.Text);
|
||||
});
|
||||
@@ -572,7 +572,7 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
|
||||
await rpcProtocol.sync();
|
||||
const value = await getOccurrencesAtPosition(languageFeaturesService.documentHighlightProvider, model, new EditorPosition(1, 2), CancellationToken.None);
|
||||
assert.strictEqual(value!.length, 1);
|
||||
assert.strictEqual(value!.size, 1);
|
||||
});
|
||||
|
||||
// --- references
|
||||
|
||||
@@ -33,7 +33,6 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
|
||||
import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
@@ -102,10 +101,6 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
|
||||
this._disposables.add(this._diffEditor.onDidUpdateDiff(() => { onDidChangeDiff(); }));
|
||||
}
|
||||
|
||||
const highlighter = WordHighlighterContribution.get(editor);
|
||||
if (highlighter) {
|
||||
this._disposables.add(highlighter.linkWordHighlighters(this._diffEditor.getModifiedEditor()));
|
||||
}
|
||||
|
||||
const doStyle = () => {
|
||||
const theme = themeService.getColorTheme();
|
||||
|
||||
Reference in New Issue
Block a user