Path: blob/main/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts
5274 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 { IDisposable, Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';6import { 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;46readonly onDidChangeStickyScrollHeight: 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;77private _cursorPositionListener: IDisposable | undefined;78private _positionLineNumber: number | undefined;7980private readonly _onDidChangeStickyScrollHeight = this._register(new Emitter<{ height: number }>());81public readonly onDidChangeStickyScrollHeight = this._onDidChangeStickyScrollHeight.event;8283constructor(84private readonly _editor: ICodeEditor,85@IContextMenuService private readonly _contextMenuService: IContextMenuService,86@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,87@IInstantiationService private readonly _instaService: IInstantiationService,88@ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService,89@ILanguageFeatureDebounceService _languageFeatureDebounceService: ILanguageFeatureDebounceService,90@IContextKeyService private readonly _contextKeyService: IContextKeyService91) {92super();93this._stickyScrollWidget = new StickyScrollWidget(this._editor);94this._stickyLineCandidateProvider = new StickyLineCandidateProvider(this._editor, _languageFeaturesService, _languageConfigurationService);95this._register(this._stickyScrollWidget);96this._register(this._stickyLineCandidateProvider);9798this._widgetState = StickyScrollWidgetState.Empty;99const stickyScrollDomNode = this._stickyScrollWidget.getDomNode();100this._register(this._editor.onDidChangeLineHeight((e) => {101e.changes.forEach((change) => {102const lineNumber = change.lineNumber;103if (this._widgetState.startLineNumbers.includes(lineNumber)) {104this._renderStickyScroll(lineNumber);105}106});107}));108this._register(this._editor.onDidChangeFont((e) => {109e.changes.forEach((change) => {110const lineNumber = change.lineNumber;111if (this._widgetState.startLineNumbers.includes(lineNumber)) {112this._renderStickyScroll(lineNumber);113}114});115}));116this._register(this._editor.onDidChangeConfiguration(e => {117this._readConfigurationChange(e);118}));119this._register(dom.addDisposableListener(stickyScrollDomNode, dom.EventType.CONTEXT_MENU, async (event: MouseEvent) => {120this._onContextMenu(dom.getWindow(stickyScrollDomNode), event);121}));122this._stickyScrollFocusedContextKey = EditorContextKeys.stickyScrollFocused.bindTo(this._contextKeyService);123this._stickyScrollVisibleContextKey = EditorContextKeys.stickyScrollVisible.bindTo(this._contextKeyService);124const focusTracker = this._register(dom.trackFocus(stickyScrollDomNode));125this._register(focusTracker.onDidBlur(_ => {126// Suppose that the blurring is caused by scrolling, then keep the focus on the sticky scroll127// This is determined by the fact that the height of the widget has become zero and there has been no position revealing128if (this._positionRevealed === false && stickyScrollDomNode.clientHeight === 0) {129this._focusedStickyElementIndex = -1;130this.focus();131132}133// In all other casees, dispose the focus on the sticky scroll134else {135this._disposeFocusStickyScrollStore();136}137}));138this._register(focusTracker.onDidFocus(_ => {139this.focus();140}));141this._registerMouseListeners();142// 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 position143this._register(dom.addDisposableListener(stickyScrollDomNode, dom.EventType.MOUSE_DOWN, (e) => {144this._onMouseDown = true;145}));146this._register(this._stickyScrollWidget.onDidChangeStickyScrollHeight((e) => {147this._onDidChangeStickyScrollHeight.fire(e);148}));149this._onDidResize();150this._readConfiguration();151}152153get stickyScrollCandidateProvider(): IStickyLineCandidateProvider {154return this._stickyLineCandidateProvider;155}156157get stickyScrollWidgetState(): StickyScrollWidgetState {158return this._widgetState;159}160161get stickyScrollWidgetHeight(): number {162return this._stickyScrollWidget.height;163}164165public static get(editor: ICodeEditor): IStickyScrollController | null {166return editor.getContribution<StickyScrollController>(StickyScrollController.ID);167}168169private _disposeFocusStickyScrollStore() {170this._stickyScrollFocusedContextKey.set(false);171this._focusDisposableStore?.dispose();172this._focused = false;173this._positionRevealed = false;174this._onMouseDown = false;175}176177public isFocused(): boolean {178return this._focused;179}180181public focus(): void {182// If the mouse is down, do not focus on the sticky scroll183if (this._onMouseDown) {184this._onMouseDown = false;185this._editor.focus();186return;187}188const focusState = this._stickyScrollFocusedContextKey.get();189if (focusState === true) {190return;191}192this._focused = true;193this._focusDisposableStore = new DisposableStore();194this._stickyScrollFocusedContextKey.set(true);195this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumbers.length - 1;196this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);197}198199public focusNext(): void {200if (this._focusedStickyElementIndex < this._stickyScrollWidget.lineNumberCount - 1) {201this._focusNav(true);202}203}204205public focusPrevious(): void {206if (this._focusedStickyElementIndex > 0) {207this._focusNav(false);208}209}210211public selectEditor(): void {212this._editor.focus();213}214215// True is next, false is previous216private _focusNav(direction: boolean): void {217this._focusedStickyElementIndex = direction ? this._focusedStickyElementIndex + 1 : this._focusedStickyElementIndex - 1;218this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);219}220221public goToFocused(): void {222const lineNumbers = this._stickyScrollWidget.lineNumbers;223this._disposeFocusStickyScrollStore();224this._revealPosition({ lineNumber: lineNumbers[this._focusedStickyElementIndex], column: 1 });225}226227private _revealPosition(position: IPosition): void {228this._reveaInEditor(position, () => this._editor.revealPosition(position));229}230231private _revealLineInCenterIfOutsideViewport(position: IPosition): void {232this._reveaInEditor(position, () => this._editor.revealLineInCenterIfOutsideViewport(position.lineNumber, ScrollType.Smooth));233}234235private _reveaInEditor(position: IPosition, revealFunction: () => void): void {236if (this._focused) {237this._disposeFocusStickyScrollStore();238}239this._positionRevealed = true;240revealFunction();241this._editor.setSelection(Range.fromPositions(position));242this._editor.focus();243}244245private _registerMouseListeners(): void {246247const sessionStore = this._register(new DisposableStore());248const gesture = this._register(new ClickLinkGesture(this._editor, {249extractLineNumberFromMouseEvent: (e) => {250const position = this._stickyScrollWidget.getEditorPositionFromNode(e.target.element);251return position ? position.lineNumber : 0;252}253}));254255const getMouseEventTarget = (mouseEvent: ClickLinkMouseEvent): { range: Range; textElement: HTMLElement } | null => {256if (!this._editor.hasModel()) {257return null;258}259if (mouseEvent.target.type !== MouseTargetType.OVERLAY_WIDGET || mouseEvent.target.detail !== this._stickyScrollWidget.getId()) {260// not hovering over our widget261return null;262}263const mouseTargetElement = mouseEvent.target.element;264if (!mouseTargetElement || mouseTargetElement.innerText !== mouseTargetElement.innerHTML) {265// not on a span element rendering text266return null;267}268const position = this._stickyScrollWidget.getEditorPositionFromNode(mouseTargetElement);269if (!position) {270// not hovering a sticky scroll line271return null;272}273return {274range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + mouseTargetElement.innerText.length),275textElement: mouseTargetElement276};277};278279const stickyScrollWidgetDomNode = this._stickyScrollWidget.getDomNode();280this._register(dom.addStandardDisposableListener(stickyScrollWidgetDomNode, dom.EventType.CLICK, (mouseEvent: IMouseEvent) => {281if (mouseEvent.ctrlKey || mouseEvent.altKey || mouseEvent.metaKey) {282// modifier pressed283return;284}285if (!mouseEvent.leftButton) {286// not left click287return;288}289if (mouseEvent.shiftKey) {290// shift click291const lineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(mouseEvent.target);292if (lineIndex === null) {293return;294}295const position = new Position(this._endLineNumbers[lineIndex], 1);296this._revealLineInCenterIfOutsideViewport(position);297return;298}299const isInFoldingIconDomNode = this._stickyScrollWidget.isInFoldingIconDomNode(mouseEvent.target);300if (isInFoldingIconDomNode) {301// clicked on folding icon302const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target);303this._toggleFoldingRegionForLine(lineNumber);304return;305}306const isInStickyLine = this._stickyScrollWidget.isInStickyLine(mouseEvent.target);307if (!isInStickyLine) {308return;309}310// normal click311let position = this._stickyScrollWidget.getEditorPositionFromNode(mouseEvent.target);312if (!position) {313const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target);314if (lineNumber === null) {315// not hovering a sticky scroll line316return;317}318position = new Position(lineNumber, 1);319}320this._revealPosition(position);321}));322this._register(dom.addDisposableListener(mainWindow, dom.EventType.MOUSE_MOVE, mouseEvent => {323this._mouseTarget = mouseEvent.target;324this._onMouseMoveOrKeyDown(mouseEvent);325}));326this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_DOWN, mouseEvent => {327this._onMouseMoveOrKeyDown(mouseEvent);328}));329this._register(dom.addDisposableListener(mainWindow, dom.EventType.KEY_UP, () => {330if (this._showEndForLine !== undefined) {331this._showEndForLine = undefined;332this._renderStickyScroll();333}334}));335336this._register(gesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, _keyboardEvent]) => {337const mouseTarget = getMouseEventTarget(mouseEvent);338if (!mouseTarget || !mouseEvent.hasTriggerModifier || !this._editor.hasModel()) {339sessionStore.clear();340return;341}342const { range, textElement } = mouseTarget;343344if (!range.equalsRange(this._stickyRangeProjectedOnEditor)) {345this._stickyRangeProjectedOnEditor = range;346sessionStore.clear();347} else if (textElement.style.textDecoration === 'underline') {348return;349}350351const cancellationToken = new CancellationTokenSource();352sessionStore.add(toDisposable(() => cancellationToken.dispose(true)));353354let currentHTMLChild: HTMLElement;355356getDefinitionsAtPosition(this._languageFeaturesService.definitionProvider, this._editor.getModel(), new Position(range.startLineNumber, range.startColumn + 1), false, cancellationToken.token).then((candidateDefinitions => {357if (cancellationToken.token.isCancellationRequested) {358return;359}360if (candidateDefinitions.length !== 0) {361this._candidateDefinitionsLength = candidateDefinitions.length;362const childHTML: HTMLElement = textElement;363if (currentHTMLChild !== childHTML) {364sessionStore.clear();365currentHTMLChild = childHTML;366currentHTMLChild.style.textDecoration = 'underline';367sessionStore.add(toDisposable(() => {368currentHTMLChild.style.textDecoration = 'none';369}));370} else if (!currentHTMLChild) {371currentHTMLChild = childHTML;372currentHTMLChild.style.textDecoration = 'underline';373sessionStore.add(toDisposable(() => {374currentHTMLChild.style.textDecoration = 'none';375}));376}377} else {378sessionStore.clear();379}380}));381}));382this._register(gesture.onCancel(() => {383sessionStore.clear();384}));385this._register(gesture.onExecute(async e => {386if (e.target.type !== MouseTargetType.OVERLAY_WIDGET || e.target.detail !== this._stickyScrollWidget.getId()) {387// not hovering over our widget388return;389}390const position = this._stickyScrollWidget.getEditorPositionFromNode(e.target.element);391if (!position) {392// not hovering a sticky scroll line393return;394}395if (!this._editor.hasModel() || !this._stickyRangeProjectedOnEditor) {396return;397}398if (this._candidateDefinitionsLength > 1) {399if (this._focused) {400this._disposeFocusStickyScrollStore();401}402this._revealPosition({ lineNumber: position.lineNumber, column: 1 });403}404this._instaService.invokeFunction(goToDefinitionWithLocation, e, this._editor, { uri: this._editor.getModel().uri, range: this._stickyRangeProjectedOnEditor });405}));406}407408private _onContextMenu(targetWindow: Window, e: MouseEvent) {409const event = new StandardMouseEvent(targetWindow, e);410411this._contextMenuService.showContextMenu({412menuId: MenuId.StickyScrollContext,413getAnchor: () => event,414menuActionOptions: { renderShortTitle: true },415});416}417418private _onMouseMoveOrKeyDown(mouseEvent: KeyboardEvent | MouseEvent): void {419if (!mouseEvent.shiftKey) {420return;421}422if (!this._mouseTarget || !dom.isHTMLElement(this._mouseTarget)) {423return;424}425const currentEndForLineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(this._mouseTarget);426if (currentEndForLineIndex === null || this._showEndForLine === currentEndForLineIndex) {427return;428}429this._showEndForLine = currentEndForLineIndex;430this._renderStickyScroll();431}432433private _toggleFoldingRegionForLine(line: number | null) {434if (!this._foldingModel || line === null) {435return;436}437const stickyLine = this._stickyScrollWidget.getRenderedStickyLine(line);438const foldingIcon = stickyLine?.foldingIcon;439if (!foldingIcon) {440return;441}442toggleCollapseState(this._foldingModel, 1, [line]);443foldingIcon.isCollapsed = !foldingIcon.isCollapsed;444const scrollTop = (foldingIcon.isCollapsed ?445this._editor.getTopForLineNumber(foldingIcon.foldingEndLine)446: this._editor.getTopForLineNumber(foldingIcon.foldingStartLine))447- this._editor.getOption(EditorOption.lineHeight) * stickyLine.index + 1;448this._editor.setScrollTop(scrollTop);449this._renderStickyScroll(line);450}451452private _readConfiguration() {453const options = this._editor.getOption(EditorOption.stickyScroll);454if (options.enabled === false) {455this._editor.removeOverlayWidget(this._stickyScrollWidget);456this._resetState();457this._sessionStore.clear();458this._enabled = false;459return;460} else if (options.enabled && !this._enabled) {461// When sticky scroll was just enabled, add the listeners on the sticky scroll462this._editor.addOverlayWidget(this._stickyScrollWidget);463this._sessionStore.add(this._editor.onDidScrollChange((e) => {464if (e.scrollTopChanged) {465this._showEndForLine = undefined;466this._renderStickyScroll();467}468}));469this._sessionStore.add(this._editor.onDidLayoutChange(() => this._onDidResize()));470this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e)));471this._sessionStore.add(this._stickyLineCandidateProvider.onDidChangeStickyScroll(() => {472this._showEndForLine = undefined;473this._renderStickyScroll();474}));475this._enabled = true;476}477478const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers);479if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {480if (!this._cursorPositionListener) {481this._cursorPositionListener = this._editor.onDidChangeCursorPosition((e) => {482if (this._positionLineNumber === e.position.lineNumber) {483return;484}485this._positionLineNumber = e.position.lineNumber;486this._showEndForLine = undefined;487this._renderStickyScroll(0);488});489this._sessionStore.add(this._cursorPositionListener);490}491} else if (this._cursorPositionListener) {492this._sessionStore.delete(this._cursorPositionListener);493this._cursorPositionListener.dispose();494this._cursorPositionListener = undefined;495}496}497498private _readConfigurationChange(event: ConfigurationChangedEvent) {499if (500event.hasChanged(EditorOption.stickyScroll)501|| event.hasChanged(EditorOption.minimap)502|| event.hasChanged(EditorOption.lineHeight)503|| event.hasChanged(EditorOption.showFoldingControls)504|| event.hasChanged(EditorOption.lineNumbers)505) {506this._readConfiguration();507}508509if (event.hasChanged(EditorOption.lineNumbers) || event.hasChanged(EditorOption.folding) || event.hasChanged(EditorOption.showFoldingControls)) {510this._renderStickyScroll(0);511}512}513514private _needsUpdate(event: IModelTokensChangedEvent) {515const stickyLineNumbers = this._stickyScrollWidget.getCurrentLines();516for (const stickyLineNumber of stickyLineNumbers) {517for (const range of event.ranges) {518if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) {519return true;520}521}522}523return false;524}525526private _onTokensChange(event: IModelTokensChangedEvent) {527if (this._needsUpdate(event)) {528// Rebuilding the whole widget from line 0529this._renderStickyScroll(0);530}531}532533private _onDidResize() {534const layoutInfo = this._editor.getLayoutInfo();535// Make sure sticky scroll doesn't take up more than 25% of the editor536const theoreticalLines = layoutInfo.height / this._editor.getOption(EditorOption.lineHeight);537this._maxStickyLines = Math.round(theoreticalLines * .25);538this._renderStickyScroll(0);539}540541private async _renderStickyScroll(rebuildFromLine?: number): Promise<void> {542const model = this._editor.getModel();543if (!model || model.isTooLargeForTokenization()) {544this._resetState();545return;546}547const nextRebuildFromLine = this._updateAndGetMinRebuildFromLine(rebuildFromLine);548const stickyWidgetVersion = this._stickyLineCandidateProvider.getVersionId();549const shouldUpdateState = stickyWidgetVersion === undefined || stickyWidgetVersion === model.getVersionId();550if (shouldUpdateState) {551if (!this._focused) {552await this._updateState(nextRebuildFromLine);553} else {554// Suppose that previously the sticky scroll widget had height 0, then if there are visible lines, set the last line as focused555if (this._focusedStickyElementIndex === -1) {556await this._updateState(nextRebuildFromLine);557this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1;558if (this._focusedStickyElementIndex !== -1) {559this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);560}561} else {562const focusedStickyElementLineNumber = this._stickyScrollWidget.lineNumbers[this._focusedStickyElementIndex];563await this._updateState(nextRebuildFromLine);564// Suppose that after setting the state, there are no sticky lines, set the focused index to -1565if (this._stickyScrollWidget.lineNumberCount === 0) {566this._focusedStickyElementIndex = -1;567} else {568const previousFocusedLineNumberExists = this._stickyScrollWidget.lineNumbers.includes(focusedStickyElementLineNumber);569570// If the line number is still there, do not change anything571// If the line number is not there, set the new focused line to be the last line572if (!previousFocusedLineNumberExists) {573this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1;574}575this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex);576}577}578}579}580}581582private _updateAndGetMinRebuildFromLine(rebuildFromLine: number | undefined): number | undefined {583if (rebuildFromLine !== undefined) {584const minRebuildFromLineOrInfinity = this._minRebuildFromLine !== undefined ? this._minRebuildFromLine : Infinity;585this._minRebuildFromLine = Math.min(rebuildFromLine, minRebuildFromLineOrInfinity);586}587return this._minRebuildFromLine;588}589590private async _updateState(rebuildFromLine?: number): Promise<void> {591this._minRebuildFromLine = undefined;592this._foldingModel = await FoldingController.get(this._editor)?.getFoldingModel() ?? undefined;593this._widgetState = this.findScrollWidgetState();594const stickyWidgetHasLines = this._widgetState.startLineNumbers.length > 0;595this._stickyScrollVisibleContextKey.set(stickyWidgetHasLines);596this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine);597}598599private async _resetState(): Promise<void> {600this._minRebuildFromLine = undefined;601this._foldingModel = undefined;602this._widgetState = StickyScrollWidgetState.Empty;603this._stickyScrollVisibleContextKey.set(false);604this._stickyScrollWidget.setState(undefined, undefined);605}606607findScrollWidgetState(): StickyScrollWidgetState {608const maxNumberStickyLines = Math.min(this._maxStickyLines, this._editor.getOption(EditorOption.stickyScroll).maxLineCount);609const scrollTop: number = this._editor.getScrollTop();610let lastLineRelativePosition: number = 0;611const startLineNumbers: number[] = [];612const endLineNumbers: number[] = [];613const arrayVisibleRanges = this._editor.getVisibleRanges();614if (arrayVisibleRanges.length !== 0) {615const fullVisibleRange = new StickyRange(arrayVisibleRanges[0].startLineNumber, arrayVisibleRanges[arrayVisibleRanges.length - 1].endLineNumber);616const candidateRanges = this._stickyLineCandidateProvider.getCandidateStickyLinesIntersecting(fullVisibleRange);617for (const range of candidateRanges) {618const start = range.startLineNumber;619const end = range.endLineNumber;620const topOfElement = range.top;621const bottomOfElement = topOfElement + range.height;622const topOfBeginningLine = this._editor.getTopForLineNumber(start) - scrollTop;623const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop;624if (topOfElement > topOfBeginningLine && topOfElement <= bottomOfEndLine) {625startLineNumbers.push(start);626endLineNumbers.push(end + 1);627if (bottomOfElement > bottomOfEndLine) {628lastLineRelativePosition = bottomOfEndLine - bottomOfElement;629}630}631if (startLineNumbers.length === maxNumberStickyLines) {632break;633}634}635}636this._endLineNumbers = endLineNumbers;637return new StickyScrollWidgetState(startLineNumbers, endLineNumbers, lastLineRelativePosition, this._showEndForLine);638}639640override dispose(): void {641super.dispose();642this._sessionStore.dispose();643}644}645646647