Path: blob/main/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';6import { IActiveCodeEditor, ICodeEditor, MouseTargetType } from '../../../browser/editorBrowser.js';7import { IEditorContribution, ScrollType } from '../../../common/editorCommon.js';8import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';9import { EditorOption, RenderLineNumbersType, ConfigurationChangedEvent } from '../../../common/config/editorOptions.js';10import { StickyScrollWidget, StickyScrollWidgetState } from './stickyScrollWidget.js';11import { IStickyLineCandidateProvider, StickyLineCandidateProvider } from './stickyScrollProvider.js';12import { IModelTokensChangedEvent } from '../../../common/textModelEvents.js';13import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';14import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';15import { MenuId } from '../../../../platform/actions/common/actions.js';16import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';17import { EditorContextKeys } from '../../../common/editorContextKeys.js';18import { ClickLinkGesture, ClickLinkMouseEvent } from '../../gotoSymbol/browser/link/clickLinkGesture.js';19import { IRange, Range } from '../../../common/core/range.js';20import { getDefinitionsAtPosition } from '../../gotoSymbol/browser/goToSymbol.js';21import { goToDefinitionWithLocation } from '../../inlayHints/browser/inlayHintsLocations.js';22import { IPosition, Position } from '../../../common/core/position.js';23import { CancellationTokenSource } from '../../../../base/common/cancellation.js';24import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';25import { ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';26import * as dom from '../../../../base/browser/dom.js';27import { StickyRange } from './stickyScrollElement.js';28import { IMouseEvent, StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';29import { FoldingController } from '../../folding/browser/folding.js';30import { FoldingModel, toggleCollapseState } from '../../folding/browser/foldingModel.js';31import { Emitter, Event } from '../../../../base/common/event.js';32import { mainWindow } from '../../../../base/browser/window.js';3334export interface IStickyScrollController {35get stickyScrollCandidateProvider(): IStickyLineCandidateProvider;36get stickyScrollWidgetState(): StickyScrollWidgetState;37readonly stickyScrollWidgetHeight: number;38isFocused(): boolean;39focus(): void;40focusNext(): void;41focusPrevious(): void;42goToFocused(): void;43findScrollWidgetState(): StickyScrollWidgetState;44dispose(): void;45selectEditor(): void;46onDidChangeStickyScrollHeight: Event<{ height: number }>;47}4849export class StickyScrollController extends Disposable implements IEditorContribution, IStickyScrollController {5051static readonly ID = 'store.contrib.stickyScrollController';5253private readonly _stickyScrollWidget: StickyScrollWidget;54private readonly _stickyLineCandidateProvider: IStickyLineCandidateProvider;55private readonly _sessionStore: DisposableStore = new DisposableStore();5657private _widgetState: StickyScrollWidgetState;58private _foldingModel: FoldingModel | undefined;59private _maxStickyLines: number = Number.MAX_SAFE_INTEGER;6061private _stickyRangeProjectedOnEditor: IRange | undefined;62private _candidateDefinitionsLength: number = -1;6364private _stickyScrollFocusedContextKey: IContextKey<boolean>;65private _stickyScrollVisibleContextKey: IContextKey<boolean>;6667private _focusDisposableStore: DisposableStore | undefined;68private _focusedStickyElementIndex: number = -1;69private _enabled = false;70private _focused = false;71private _positionRevealed = false;72private _onMouseDown = false;73private _endLineNumbers: number[] = [];74private _showEndForLine: number | undefined;75private _minRebuildFromLine: number | undefined;76private _mouseTarget: EventTarget | null = null;7778private readonly _onDidChangeStickyScrollHeight = this._register(new Emitter<{ height: number }>());79public readonly onDidChangeStickyScrollHeight = this._onDidChangeStickyScrollHeight.event;8081constructor(82private readonly _editor: ICodeEditor,83@IContextMenuService private readonly _contextMenuService: IContextMenuService,84@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,85@IInstantiationService private readonly _instaService: IInstantiationService,86@ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService,87@ILanguageFeatureDebounceService _languageFeatureDebounceService: ILanguageFeatureDebounceService,88@IContextKeyService private readonly _contextKeyService: IContextKeyService89) {90super();91this._stickyScrollWidget = new StickyScrollWidget(this._editor);92this._stickyLineCandidateProvider = new StickyLineCandidateProvider(this._editor, _languageFeaturesService, _languageConfigurationService);93this._register(this._stickyScrollWidget);94this._register(this._stickyLineCandidateProvider);9596this._widgetState = StickyScrollWidgetState.Empty;97const stickyScrollDomNode = this._stickyScrollWidget.getDomNode();98this._register(this._editor.onDidChangeLineHeight((e) => {99e.changes.forEach((change) => {100const lineNumber = change.lineNumber;101if (this._widgetState.startLineNumbers.includes(lineNumber)) {102this._renderStickyScroll(lineNumber);103}104});105}));106this._register(this._editor.onDidChangeFont((e) => {107e.changes.forEach((change) => {108const lineNumber = change.lineNumber;109if (this._widgetState.startLineNumbers.includes(lineNumber)) {110this._renderStickyScroll(lineNumber);111}112});113}));114this._register(this._editor.onDidChangeConfiguration(e => {115this._readConfigurationChange(e);116}));117this._register(dom.addDisposableListener(stickyScrollDomNode, dom.EventType.CONTEXT_MENU, async (event: MouseEvent) => {118this._onContextMenu(dom.getWindow(stickyScrollDomNode), event);119}));120this._stickyScrollFocusedContextKey = EditorContextKeys.stickyScrollFocused.bindTo(this._contextKeyService);121this._stickyScrollVisibleContextKey = EditorContextKeys.stickyScrollVisible.bindTo(this._contextKeyService);122const focusTracker = this._register(dom.trackFocus(stickyScrollDomNode));123this._register(focusTracker.onDidBlur(_ => {124// Suppose that the blurring is caused by scrolling, then keep the focus on the sticky scroll125// This is determined by the fact that the height of the widget has become zero and there has been no position revealing126if (this._positionRevealed === false && stickyScrollDomNode.clientHeight === 0) {127this._focusedStickyElementIndex = -1;128this.focus();129130}131// In all other casees, dispose the focus on the sticky scroll132else {133this._disposeFocusStickyScrollStore();134}135}));136this._register(focusTracker.onDidFocus(_ => {137this.focus();138}));139this._registerMouseListeners();140// Suppose that mouse down on the sticky scroll, then do not focus on the sticky scroll because this will be followed by the revealing of a position141this._register(dom.addDisposableListener(stickyScrollDomNode, dom.EventType.MOUSE_DOWN, (e) => {142this._onMouseDown = true;143}));144this._register(this._stickyScrollWidget.onDidChangeStickyScrollHeight((e) => {145this._onDidChangeStickyScrollHeight.fire(e);146}));147this._onDidResize();148this._readConfiguration();149}150151get stickyScrollCandidateProvider(): IStickyLineCandidateProvider {152return this._stickyLineCandidateProvider;153}154155get stickyScrollWidgetState(): StickyScrollWidgetState {156return this._widgetState;157}158159get stickyScrollWidgetHeight(): number {160return this._stickyScrollWidget.height;161}162163public static get(editor: ICodeEditor): IStickyScrollController | null {164return editor.getContribution<StickyScrollController>(StickyScrollController.ID);165}166167private _disposeFocusStickyScrollStore() {168this._stickyScrollFocusedContextKey.set(false);169this._focusDisposableStore?.dispose();170this._focused = false;171this._positionRevealed = false;172this._onMouseDown = false;173}174175public isFocused(): boolean {176return this._focused;177}178179public focus(): void {180// If the mouse is down, do not focus on the sticky scroll181if (this._onMouseDown) {182this._onMouseDown = false;183this._editor.focus();184return;185}186const focusState = this._stickyScrollFocusedContextKey.get();187if (focusState === true) {188return;189}190this._focused = true;191this._focusDisposableStore = new DisposableStore();192this._stickyScrollFocusedContextKey.set(true);193this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumbers.length - 1;194this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);195}196197public focusNext(): void {198if (this._focusedStickyElementIndex < this._stickyScrollWidget.lineNumberCount - 1) {199this._focusNav(true);200}201}202203public focusPrevious(): void {204if (this._focusedStickyElementIndex > 0) {205this._focusNav(false);206}207}208209public selectEditor(): void {210this._editor.focus();211}212213// True is next, false is previous214private _focusNav(direction: boolean): void {215this._focusedStickyElementIndex = direction ? this._focusedStickyElementIndex + 1 : this._focusedStickyElementIndex - 1;216this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);217}218219public goToFocused(): void {220const lineNumbers = this._stickyScrollWidget.lineNumbers;221this._disposeFocusStickyScrollStore();222this._revealPosition({ lineNumber: lineNumbers[this._focusedStickyElementIndex], column: 1 });223}224225private _revealPosition(position: IPosition): void {226this._reveaInEditor(position, () => this._editor.revealPosition(position));227}228229private _revealLineInCenterIfOutsideViewport(position: IPosition): void {230this._reveaInEditor(position, () => this._editor.revealLineInCenterIfOutsideViewport(position.lineNumber, ScrollType.Smooth));231}232233private _reveaInEditor(position: IPosition, revealFunction: () => void): void {234if (this._focused) {235this._disposeFocusStickyScrollStore();236}237this._positionRevealed = true;238revealFunction();239this._editor.setSelection(Range.fromPositions(position));240this._editor.focus();241}242243private _registerMouseListeners(): void {244245const sessionStore = this._register(new DisposableStore());246const gesture = this._register(new ClickLinkGesture(this._editor, {247extractLineNumberFromMouseEvent: (e) => {248const position = this._stickyScrollWidget.getEditorPositionFromNode(e.target.element);249return position ? position.lineNumber : 0;250}251}));252253const getMouseEventTarget = (mouseEvent: ClickLinkMouseEvent): { range: Range; textElement: HTMLElement } | null => {254if (!this._editor.hasModel()) {255return null;256}257if (mouseEvent.target.type !== MouseTargetType.OVERLAY_WIDGET || mouseEvent.target.detail !== this._stickyScrollWidget.getId()) {258// not hovering over our widget259return null;260}261const mouseTargetElement = mouseEvent.target.element;262if (!mouseTargetElement || mouseTargetElement.innerText !== mouseTargetElement.innerHTML) {263// not on a span element rendering text264return null;265}266const position = this._stickyScrollWidget.getEditorPositionFromNode(mouseTargetElement);267if (!position) {268// not hovering a sticky scroll line269return null;270}271return {272range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + mouseTargetElement.innerText.length),273textElement: mouseTargetElement274};275};276277const stickyScrollWidgetDomNode = this._stickyScrollWidget.getDomNode();278this._register(dom.addStandardDisposableListener(stickyScrollWidgetDomNode, dom.EventType.CLICK, (mouseEvent: IMouseEvent) => {279if (mouseEvent.ctrlKey || mouseEvent.altKey || mouseEvent.metaKey) {280// modifier pressed281return;282}283if (!mouseEvent.leftButton) {284// not left click285return;286}287if (mouseEvent.shiftKey) {288// shift click289const lineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(mouseEvent.target);290if (lineIndex === null) {291return;292}293const position = new Position(this._endLineNumbers[lineIndex], 1);294this._revealLineInCenterIfOutsideViewport(position);295return;296}297const isInFoldingIconDomNode = this._stickyScrollWidget.isInFoldingIconDomNode(mouseEvent.target);298if (isInFoldingIconDomNode) {299// clicked on folding icon300const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target);301this._toggleFoldingRegionForLine(lineNumber);302return;303}304const isInStickyLine = this._stickyScrollWidget.isInStickyLine(mouseEvent.target);305if (!isInStickyLine) {306return;307}308// normal click309let position = this._stickyScrollWidget.getEditorPositionFromNode(mouseEvent.target);310if (!position) {311const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target);312if (lineNumber === null) {313// not hovering a sticky scroll line314return;315}316position = new Position(lineNumber, 1);317}318this._revealPosition(position);319}));320this._register(dom.addDisposableListener(mainWindow, dom.EventType.MOUSE_MOVE, mouseEvent => {321this._mouseTarget = mouseEvent.target;322this._onMouseMoveOrKeyDown(mouseEvent);323}));324this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_DOWN, mouseEvent => {325this._onMouseMoveOrKeyDown(mouseEvent);326}));327this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_UP, () => {328if (this._showEndForLine !== undefined) {329this._showEndForLine = undefined;330this._renderStickyScroll();331}332}));333334this._register(gesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, _keyboardEvent]) => {335const mouseTarget = getMouseEventTarget(mouseEvent);336if (!mouseTarget || !mouseEvent.hasTriggerModifier || !this._editor.hasModel()) {337sessionStore.clear();338return;339}340const { range, textElement } = mouseTarget;341342if (!range.equalsRange(this._stickyRangeProjectedOnEditor)) {343this._stickyRangeProjectedOnEditor = range;344sessionStore.clear();345} else if (textElement.style.textDecoration === 'underline') {346return;347}348349const cancellationToken = new CancellationTokenSource();350sessionStore.add(toDisposable(() => cancellationToken.dispose(true)));351352let currentHTMLChild: HTMLElement;353354getDefinitionsAtPosition(this._languageFeaturesService.definitionProvider, this._editor.getModel(), new Position(range.startLineNumber, range.startColumn + 1), false, cancellationToken.token).then((candidateDefinitions => {355if (cancellationToken.token.isCancellationRequested) {356return;357}358if (candidateDefinitions.length !== 0) {359this._candidateDefinitionsLength = candidateDefinitions.length;360const childHTML: HTMLElement = textElement;361if (currentHTMLChild !== childHTML) {362sessionStore.clear();363currentHTMLChild = childHTML;364currentHTMLChild.style.textDecoration = 'underline';365sessionStore.add(toDisposable(() => {366currentHTMLChild.style.textDecoration = 'none';367}));368} else if (!currentHTMLChild) {369currentHTMLChild = childHTML;370currentHTMLChild.style.textDecoration = 'underline';371sessionStore.add(toDisposable(() => {372currentHTMLChild.style.textDecoration = 'none';373}));374}375} else {376sessionStore.clear();377}378}));379}));380this._register(gesture.onCancel(() => {381sessionStore.clear();382}));383this._register(gesture.onExecute(async e => {384if (e.target.type !== MouseTargetType.OVERLAY_WIDGET || e.target.detail !== this._stickyScrollWidget.getId()) {385// not hovering over our widget386return;387}388const position = this._stickyScrollWidget.getEditorPositionFromNode(e.target.element);389if (!position) {390// not hovering a sticky scroll line391return;392}393if (!this._editor.hasModel() || !this._stickyRangeProjectedOnEditor) {394return;395}396if (this._candidateDefinitionsLength > 1) {397if (this._focused) {398this._disposeFocusStickyScrollStore();399}400this._revealPosition({ lineNumber: position.lineNumber, column: 1 });401}402this._instaService.invokeFunction(goToDefinitionWithLocation, e, this._editor as IActiveCodeEditor, { uri: this._editor.getModel().uri, range: this._stickyRangeProjectedOnEditor });403}));404}405406private _onContextMenu(targetWindow: Window, e: MouseEvent) {407const event = new StandardMouseEvent(targetWindow, e);408409this._contextMenuService.showContextMenu({410menuId: MenuId.StickyScrollContext,411getAnchor: () => event,412});413}414415private _onMouseMoveOrKeyDown(mouseEvent: KeyboardEvent | MouseEvent): void {416if (!mouseEvent.shiftKey) {417return;418}419if (!this._mouseTarget || !dom.isHTMLElement(this._mouseTarget)) {420return;421}422const currentEndForLineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(this._mouseTarget);423if (currentEndForLineIndex === null || this._showEndForLine === currentEndForLineIndex) {424return;425}426this._showEndForLine = currentEndForLineIndex;427this._renderStickyScroll();428}429430private _toggleFoldingRegionForLine(line: number | null) {431if (!this._foldingModel || line === null) {432return;433}434const stickyLine = this._stickyScrollWidget.getRenderedStickyLine(line);435const foldingIcon = stickyLine?.foldingIcon;436if (!foldingIcon) {437return;438}439toggleCollapseState(this._foldingModel, 1, [line]);440foldingIcon.isCollapsed = !foldingIcon.isCollapsed;441const scrollTop = (foldingIcon.isCollapsed ?442this._editor.getTopForLineNumber(foldingIcon.foldingEndLine)443: this._editor.getTopForLineNumber(foldingIcon.foldingStartLine))444- this._editor.getOption(EditorOption.lineHeight) * stickyLine.index + 1;445this._editor.setScrollTop(scrollTop);446this._renderStickyScroll(line);447}448449private _readConfiguration() {450const options = this._editor.getOption(EditorOption.stickyScroll);451if (options.enabled === false) {452this._editor.removeOverlayWidget(this._stickyScrollWidget);453this._resetState();454this._sessionStore.clear();455this._enabled = false;456return;457} else if (options.enabled && !this._enabled) {458// When sticky scroll was just enabled, add the listeners on the sticky scroll459this._editor.addOverlayWidget(this._stickyScrollWidget);460this._sessionStore.add(this._editor.onDidScrollChange((e) => {461if (e.scrollTopChanged) {462this._showEndForLine = undefined;463this._renderStickyScroll();464}465}));466this._sessionStore.add(this._editor.onDidLayoutChange(() => this._onDidResize()));467this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e)));468this._sessionStore.add(this._stickyLineCandidateProvider.onDidChangeStickyScroll(() => {469this._showEndForLine = undefined;470this._renderStickyScroll();471}));472this._enabled = true;473}474475const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers);476if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {477this._sessionStore.add(this._editor.onDidChangeCursorPosition(() => {478this._showEndForLine = undefined;479this._renderStickyScroll(0);480}));481}482}483484private _readConfigurationChange(event: ConfigurationChangedEvent) {485if (486event.hasChanged(EditorOption.stickyScroll)487|| event.hasChanged(EditorOption.minimap)488|| event.hasChanged(EditorOption.lineHeight)489|| event.hasChanged(EditorOption.showFoldingControls)490|| event.hasChanged(EditorOption.lineNumbers)491) {492this._readConfiguration();493}494495if (event.hasChanged(EditorOption.lineNumbers) || event.hasChanged(EditorOption.folding) || event.hasChanged(EditorOption.showFoldingControls)) {496this._renderStickyScroll(0);497}498}499500private _needsUpdate(event: IModelTokensChangedEvent) {501const stickyLineNumbers = this._stickyScrollWidget.getCurrentLines();502for (const stickyLineNumber of stickyLineNumbers) {503for (const range of event.ranges) {504if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) {505return true;506}507}508}509return false;510}511512private _onTokensChange(event: IModelTokensChangedEvent) {513if (this._needsUpdate(event)) {514// Rebuilding the whole widget from line 0515this._renderStickyScroll(0);516}517}518519private _onDidResize() {520const layoutInfo = this._editor.getLayoutInfo();521// Make sure sticky scroll doesn't take up more than 25% of the editor522const theoreticalLines = layoutInfo.height / this._editor.getOption(EditorOption.lineHeight);523this._maxStickyLines = Math.round(theoreticalLines * .25);524this._renderStickyScroll(0);525}526527private async _renderStickyScroll(rebuildFromLine?: number): Promise<void> {528const model = this._editor.getModel();529if (!model || model.isTooLargeForTokenization()) {530this._resetState();531return;532}533const nextRebuildFromLine = this._updateAndGetMinRebuildFromLine(rebuildFromLine);534const stickyWidgetVersion = this._stickyLineCandidateProvider.getVersionId();535const shouldUpdateState = stickyWidgetVersion === undefined || stickyWidgetVersion === model.getVersionId();536if (shouldUpdateState) {537if (!this._focused) {538await this._updateState(nextRebuildFromLine);539} else {540// Suppose that previously the sticky scroll widget had height 0, then if there are visible lines, set the last line as focused541if (this._focusedStickyElementIndex === -1) {542await this._updateState(nextRebuildFromLine);543this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1;544if (this._focusedStickyElementIndex !== -1) {545this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);546}547} else {548const focusedStickyElementLineNumber = this._stickyScrollWidget.lineNumbers[this._focusedStickyElementIndex];549await this._updateState(nextRebuildFromLine);550// Suppose that after setting the state, there are no sticky lines, set the focused index to -1551if (this._stickyScrollWidget.lineNumberCount === 0) {552this._focusedStickyElementIndex = -1;553} else {554const previousFocusedLineNumberExists = this._stickyScrollWidget.lineNumbers.includes(focusedStickyElementLineNumber);555556// If the line number is still there, do not change anything557// If the line number is not there, set the new focused line to be the last line558if (!previousFocusedLineNumberExists) {559this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1;560}561this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);562}563}564}565}566}567568private _updateAndGetMinRebuildFromLine(rebuildFromLine: number | undefined): number | undefined {569if (rebuildFromLine !== undefined) {570const minRebuildFromLineOrInfinity = this._minRebuildFromLine !== undefined ? this._minRebuildFromLine : Infinity;571this._minRebuildFromLine = Math.min(rebuildFromLine, minRebuildFromLineOrInfinity);572}573return this._minRebuildFromLine;574}575576private async _updateState(rebuildFromLine?: number): Promise<void> {577this._minRebuildFromLine = undefined;578this._foldingModel = await FoldingController.get(this._editor)?.getFoldingModel() ?? undefined;579this._widgetState = this.findScrollWidgetState();580const stickyWidgetHasLines = this._widgetState.startLineNumbers.length > 0;581this._stickyScrollVisibleContextKey.set(stickyWidgetHasLines);582this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine);583}584585private async _resetState(): Promise<void> {586this._minRebuildFromLine = undefined;587this._foldingModel = undefined;588this._widgetState = StickyScrollWidgetState.Empty;589this._stickyScrollVisibleContextKey.set(false);590this._stickyScrollWidget.setState(undefined, undefined);591}592593findScrollWidgetState(): StickyScrollWidgetState {594if (!this._editor.hasModel()) {595return StickyScrollWidgetState.Empty;596}597const textModel = this._editor.getModel();598const maxNumberStickyLines = Math.min(this._maxStickyLines, this._editor.getOption(EditorOption.stickyScroll).maxLineCount);599const scrollTop: number = this._editor.getScrollTop();600let lastLineRelativePosition: number = 0;601const startLineNumbers: number[] = [];602const endLineNumbers: number[] = [];603const arrayVisibleRanges = this._editor.getVisibleRanges();604if (arrayVisibleRanges.length !== 0) {605const fullVisibleRange = new StickyRange(arrayVisibleRanges[0].startLineNumber, arrayVisibleRanges[arrayVisibleRanges.length - 1].endLineNumber);606const candidateRanges = this._stickyLineCandidateProvider.getCandidateStickyLinesIntersecting(fullVisibleRange);607for (const range of candidateRanges) {608const start = range.startLineNumber;609const end = range.endLineNumber;610const isValidRange = textModel.isValidRange({ startLineNumber: start, endLineNumber: end, startColumn: 1, endColumn: 1 });611if (isValidRange && end - start > 0) {612const topOfElement = range.top;613const bottomOfElement = topOfElement + range.height;614const topOfBeginningLine = this._editor.getTopForLineNumber(start) - scrollTop;615const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop;616if (topOfElement > topOfBeginningLine && topOfElement <= bottomOfEndLine) {617startLineNumbers.push(start);618endLineNumbers.push(end + 1);619if (bottomOfElement > bottomOfEndLine) {620lastLineRelativePosition = bottomOfEndLine - bottomOfElement;621}622}623if (startLineNumbers.length === maxNumberStickyLines) {624break;625}626}627}628}629this._endLineNumbers = endLineNumbers;630return new StickyScrollWidgetState(startLineNumbers, endLineNumbers, lastLineRelativePosition, this._showEndForLine);631}632633override dispose(): void {634super.dispose();635this._sessionStore.dispose();636}637}638639640