Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts
5332 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../base/browser/dom.js';
7
import { createTrustedTypesPolicy } from '../../../../base/browser/trustedTypes.js';
8
import { equals } from '../../../../base/common/arrays.js';
9
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
10
import { ThemeIcon } from '../../../../base/common/themables.js';
11
import './stickyScroll.css';
12
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../browser/editorBrowser.js';
13
import { getColumnOfNodeOffset } from '../../../browser/viewParts/viewLines/viewLine.js';
14
import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js';
15
import { EditorLayoutInfo, EditorOption, RenderLineNumbersType } from '../../../common/config/editorOptions.js';
16
import { Position } from '../../../common/core/position.js';
17
import { StringBuilder } from '../../../common/core/stringBuilder.js';
18
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
19
import { CharacterMapping, RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
20
import { foldingCollapsedIcon, foldingExpandedIcon } from '../../folding/browser/foldingDecorations.js';
21
import { FoldingModel } from '../../folding/browser/foldingModel.js';
22
import { Emitter } from '../../../../base/common/event.js';
23
import { IViewModel } from '../../../common/viewModel.js';
24
25
export class StickyScrollWidgetState {
26
constructor(
27
readonly startLineNumbers: number[],
28
readonly endLineNumbers: number[],
29
readonly lastLineRelativePosition: number,
30
readonly showEndForLine: number | null = null
31
) { }
32
33
equals(other: StickyScrollWidgetState | undefined): boolean {
34
return !!other
35
&& this.lastLineRelativePosition === other.lastLineRelativePosition
36
&& this.showEndForLine === other.showEndForLine
37
&& equals(this.startLineNumbers, other.startLineNumbers)
38
&& equals(this.endLineNumbers, other.endLineNumbers);
39
}
40
41
static get Empty() {
42
return new StickyScrollWidgetState([], [], 0);
43
}
44
}
45
46
const _ttPolicy = createTrustedTypesPolicy('stickyScrollViewLayer', { createHTML: value => value });
47
const STICKY_INDEX_ATTR = 'data-sticky-line-index';
48
const STICKY_IS_LINE_ATTR = 'data-sticky-is-line';
49
const STICKY_IS_LINE_NUMBER_ATTR = 'data-sticky-is-line-number';
50
const STICKY_IS_FOLDING_ICON_ATTR = 'data-sticky-is-folding-icon';
51
52
export class StickyScrollWidget extends Disposable implements IOverlayWidget {
53
54
private readonly _foldingIconStore = this._register(new DisposableStore());
55
private readonly _rootDomNode: HTMLElement = document.createElement('div');
56
private readonly _lineNumbersDomNode: HTMLElement = document.createElement('div');
57
private readonly _linesDomNodeScrollable: HTMLElement = document.createElement('div');
58
private readonly _linesDomNode: HTMLElement = document.createElement('div');
59
60
private readonly _editor: ICodeEditor;
61
62
private _state: StickyScrollWidgetState | undefined;
63
private _renderedStickyLines: RenderedStickyLine[] = [];
64
private _lineNumbers: number[] = [];
65
private _lastLineRelativePosition: number = 0;
66
private _minContentWidthInPx: number = 0;
67
private _isOnGlyphMargin: boolean = false;
68
private _height: number = -1;
69
70
public get height(): number { return this._height; }
71
72
private readonly _onDidChangeStickyScrollHeight = this._register(new Emitter<{ height: number }>());
73
public readonly onDidChangeStickyScrollHeight = this._onDidChangeStickyScrollHeight.event;
74
75
constructor(
76
editor: ICodeEditor
77
) {
78
super();
79
80
this._editor = editor;
81
this._lineNumbersDomNode.className = 'sticky-widget-line-numbers';
82
this._lineNumbersDomNode.setAttribute('role', 'none');
83
84
this._linesDomNode.className = 'sticky-widget-lines';
85
this._linesDomNode.setAttribute('role', 'list');
86
87
this._linesDomNodeScrollable.className = 'sticky-widget-lines-scrollable';
88
this._linesDomNodeScrollable.appendChild(this._linesDomNode);
89
90
this._rootDomNode.className = 'sticky-widget';
91
this._rootDomNode.classList.toggle('peek', editor instanceof EmbeddedCodeEditorWidget);
92
this._rootDomNode.appendChild(this._lineNumbersDomNode);
93
this._rootDomNode.appendChild(this._linesDomNodeScrollable);
94
this._setHeight(0);
95
96
const updateScrollLeftPosition = () => {
97
this._linesDomNode.style.left = this._editor.getOption(EditorOption.stickyScroll).scrollWithEditor ? `-${this._editor.getScrollLeft()}px` : '0px';
98
};
99
this._register(this._editor.onDidChangeConfiguration((e) => {
100
if (e.hasChanged(EditorOption.stickyScroll)) {
101
updateScrollLeftPosition();
102
}
103
}));
104
this._register(this._editor.onDidScrollChange((e) => {
105
if (e.scrollLeftChanged) {
106
updateScrollLeftPosition();
107
}
108
if (e.scrollWidthChanged) {
109
this._updateWidgetWidth();
110
}
111
}));
112
this._register(this._editor.onDidChangeModel(() => {
113
updateScrollLeftPosition();
114
this._updateWidgetWidth();
115
}));
116
updateScrollLeftPosition();
117
118
this._register(this._editor.onDidLayoutChange((e) => {
119
this._updateWidgetWidth();
120
}));
121
this._updateWidgetWidth();
122
}
123
124
get lineNumbers(): number[] {
125
return this._lineNumbers;
126
}
127
128
get lineNumberCount(): number {
129
return this._lineNumbers.length;
130
}
131
132
getRenderedStickyLine(lineNumber: number): RenderedStickyLine | undefined {
133
return this._renderedStickyLines.find(stickyLine => stickyLine.lineNumber === lineNumber);
134
}
135
136
getCurrentLines(): readonly number[] {
137
return this._lineNumbers;
138
}
139
140
setState(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, rebuildFromIndexCandidate?: number): void {
141
const currentStateAndPreviousStateUndefined = !this._state && !state;
142
const currentStateDefinedAndEqualsPreviousState = this._state && this._state.equals(state);
143
if (rebuildFromIndexCandidate === undefined && (currentStateAndPreviousStateUndefined || currentStateDefinedAndEqualsPreviousState)) {
144
return;
145
}
146
const data = this._findRenderingData(state);
147
const previousLineNumbers = this._lineNumbers;
148
this._lineNumbers = data.lineNumbers;
149
this._lastLineRelativePosition = data.lastLineRelativePosition;
150
const rebuildFromIndex = this._findIndexToRebuildFrom(previousLineNumbers, this._lineNumbers, rebuildFromIndexCandidate);
151
this._renderRootNode(this._lineNumbers, this._lastLineRelativePosition, foldingModel, rebuildFromIndex);
152
this._state = state;
153
}
154
155
private _findRenderingData(state: StickyScrollWidgetState | undefined): { lineNumbers: number[]; lastLineRelativePosition: number } {
156
if (!state) {
157
return { lineNumbers: [], lastLineRelativePosition: 0 };
158
}
159
const candidateLineNumbers = [...state.startLineNumbers];
160
if (state.showEndForLine !== null) {
161
candidateLineNumbers[state.showEndForLine] = state.endLineNumbers[state.showEndForLine];
162
}
163
let totalHeight = 0;
164
for (let i = 0; i < candidateLineNumbers.length; i++) {
165
totalHeight += this._editor.getLineHeightForPosition(new Position(candidateLineNumbers[i], 1));
166
}
167
if (totalHeight === 0) {
168
return { lineNumbers: [], lastLineRelativePosition: 0 };
169
}
170
return { lineNumbers: candidateLineNumbers, lastLineRelativePosition: state.lastLineRelativePosition };
171
}
172
173
private _findIndexToRebuildFrom(previousLineNumbers: number[], newLineNumbers: number[], rebuildFromIndexCandidate?: number): number {
174
if (newLineNumbers.length === 0) {
175
return 0;
176
}
177
if (rebuildFromIndexCandidate !== undefined) {
178
return rebuildFromIndexCandidate;
179
}
180
const validIndex = newLineNumbers.findIndex(startLineNumber => !previousLineNumbers.includes(startLineNumber));
181
return validIndex === -1 ? 0 : validIndex;
182
}
183
184
private _updateWidgetWidth(): void {
185
const layoutInfo = this._editor.getLayoutInfo();
186
const lineNumbersWidth = layoutInfo.contentLeft;
187
this._lineNumbersDomNode.style.width = `${lineNumbersWidth}px`;
188
this._linesDomNodeScrollable.style.setProperty('--vscode-editorStickyScroll-scrollableWidth', `${this._editor.getScrollWidth() - layoutInfo.verticalScrollbarWidth}px`);
189
this._rootDomNode.style.width = `${layoutInfo.width - layoutInfo.verticalScrollbarWidth}px`;
190
}
191
192
private _useFoldingOpacityTransition(requireTransitions: boolean) {
193
this._lineNumbersDomNode.style.setProperty('--vscode-editorStickyScroll-foldingOpacityTransition', `opacity ${requireTransitions ? 0.5 : 0}s`);
194
}
195
196
private _setFoldingIconsVisibility(allVisible: boolean) {
197
for (const line of this._renderedStickyLines) {
198
const foldingIcon = line.foldingIcon;
199
if (!foldingIcon) {
200
continue;
201
}
202
foldingIcon.setVisible(allVisible ? true : foldingIcon.isCollapsed);
203
}
204
}
205
206
private async _renderRootNode(lineNumbers: number[], lastLineRelativePosition: number, foldingModel: FoldingModel | undefined, rebuildFromIndex: number): Promise<void> {
207
const viewModel = this._editor._getViewModel();
208
if (!viewModel) {
209
this._clearWidget();
210
return;
211
}
212
if (lineNumbers.length === 0) {
213
this._clearWidget();
214
return;
215
}
216
const renderedStickyLines: RenderedStickyLine[] = [];
217
const lastLineNumber = lineNumbers[lineNumbers.length - 1];
218
let top: number = 0;
219
for (let i = 0; i < this._renderedStickyLines.length; i++) {
220
if (i < rebuildFromIndex) {
221
const renderedLine = this._renderedStickyLines[i];
222
renderedStickyLines.push(this._updatePosition(renderedLine, top, renderedLine.lineNumber === lastLineNumber));
223
top += renderedLine.height;
224
} else {
225
const renderedLine = this._renderedStickyLines[i];
226
renderedLine.lineNumberDomNode.remove();
227
renderedLine.lineDomNode.remove();
228
}
229
}
230
const layoutInfo = this._editor.getLayoutInfo();
231
for (let i = rebuildFromIndex; i < lineNumbers.length; i++) {
232
const stickyLine = this._renderChildNode(viewModel, i, lineNumbers[i], top, lastLineNumber === lineNumbers[i], foldingModel, layoutInfo);
233
top += stickyLine.height;
234
this._linesDomNode.appendChild(stickyLine.lineDomNode);
235
this._lineNumbersDomNode.appendChild(stickyLine.lineNumberDomNode);
236
renderedStickyLines.push(stickyLine);
237
}
238
if (foldingModel) {
239
this._setFoldingHoverListeners();
240
this._useFoldingOpacityTransition(!this._isOnGlyphMargin);
241
}
242
this._minContentWidthInPx = Math.max(...this._renderedStickyLines.map(l => l.scrollWidth)) + layoutInfo.verticalScrollbarWidth;
243
this._renderedStickyLines = renderedStickyLines;
244
this._setHeight(top + lastLineRelativePosition);
245
this._editor.layoutOverlayWidget(this);
246
}
247
248
private _clearWidget(): void {
249
for (let i = 0; i < this._renderedStickyLines.length; i++) {
250
const stickyLine = this._renderedStickyLines[i];
251
stickyLine.lineNumberDomNode.remove();
252
stickyLine.lineDomNode.remove();
253
}
254
this._setHeight(0);
255
}
256
257
private _setHeight(height: number): void {
258
if (this._height === height) {
259
return;
260
}
261
this._height = height;
262
263
if (this._height === 0) {
264
this._rootDomNode.style.display = 'none';
265
} else {
266
this._rootDomNode.style.display = 'block';
267
this._lineNumbersDomNode.style.height = `${this._height}px`;
268
this._linesDomNodeScrollable.style.height = `${this._height}px`;
269
this._rootDomNode.style.height = `${this._height}px`;
270
}
271
272
this._onDidChangeStickyScrollHeight.fire({ height: this._height });
273
}
274
275
private _setFoldingHoverListeners(): void {
276
this._foldingIconStore.clear();
277
const showFoldingControls: 'mouseover' | 'always' | 'never' = this._editor.getOption(EditorOption.showFoldingControls);
278
if (showFoldingControls !== 'mouseover') {
279
return;
280
}
281
this._foldingIconStore.clear();
282
this._foldingIconStore.add(dom.addDisposableListener(this._lineNumbersDomNode, dom.EventType.MOUSE_ENTER, () => {
283
this._isOnGlyphMargin = true;
284
this._setFoldingIconsVisibility(true);
285
}));
286
this._foldingIconStore.add(dom.addDisposableListener(this._lineNumbersDomNode, dom.EventType.MOUSE_LEAVE, () => {
287
this._isOnGlyphMargin = false;
288
this._useFoldingOpacityTransition(true);
289
this._setFoldingIconsVisibility(false);
290
}));
291
}
292
293
private _renderChildNode(viewModel: IViewModel, index: number, line: number, top: number, isLastLine: boolean, foldingModel: FoldingModel | undefined, layoutInfo: EditorLayoutInfo): RenderedStickyLine {
294
295
const renderedLine = new RenderedStickyLine(
296
this._editor,
297
viewModel,
298
layoutInfo,
299
foldingModel,
300
this._isOnGlyphMargin,
301
index,
302
line
303
);
304
return this._updatePosition(renderedLine, top, isLastLine);
305
}
306
307
private _updatePosition(stickyLine: RenderedStickyLine, top: number, isLastLine: boolean): RenderedStickyLine {
308
const lineHTMLNode = stickyLine.lineDomNode;
309
const lineNumberHTMLNode = stickyLine.lineNumberDomNode;
310
if (isLastLine) {
311
const zIndex = '0';
312
lineHTMLNode.style.zIndex = zIndex;
313
lineNumberHTMLNode.style.zIndex = zIndex;
314
const updatedTop = `${top + this._lastLineRelativePosition + (stickyLine.foldingIcon?.isCollapsed ? 1 : 0)}px`;
315
lineHTMLNode.style.top = updatedTop;
316
lineNumberHTMLNode.style.top = updatedTop;
317
} else {
318
const zIndex = '1';
319
lineHTMLNode.style.zIndex = zIndex;
320
lineNumberHTMLNode.style.zIndex = zIndex;
321
lineHTMLNode.style.top = `${top}px`;
322
lineNumberHTMLNode.style.top = `${top}px`;
323
}
324
return stickyLine;
325
}
326
327
getId(): string {
328
return 'editor.contrib.stickyScrollWidget';
329
}
330
331
getDomNode(): HTMLElement {
332
return this._rootDomNode;
333
}
334
335
getPosition(): IOverlayWidgetPosition | null {
336
return {
337
preference: OverlayWidgetPositionPreference.TOP_CENTER,
338
stackOrdinal: 10,
339
};
340
}
341
342
getMinContentWidthInPx(): number {
343
return this._minContentWidthInPx;
344
}
345
346
focusLineWithIndex(index: number) {
347
if (0 <= index && index < this._renderedStickyLines.length) {
348
this._renderedStickyLines[index].lineDomNode.focus();
349
}
350
}
351
352
/**
353
* Given a leaf dom node, tries to find the editor position.
354
*/
355
getEditorPositionFromNode(spanDomNode: HTMLElement | null): Position | null {
356
if (!spanDomNode || spanDomNode.children.length > 0) {
357
// This is not a leaf node
358
return null;
359
}
360
const renderedStickyLine = this._getRenderedStickyLineFromChildDomNode(spanDomNode);
361
if (!renderedStickyLine) {
362
return null;
363
}
364
const column = getColumnOfNodeOffset(renderedStickyLine.characterMapping, spanDomNode, 0);
365
return new Position(renderedStickyLine.lineNumber, column);
366
}
367
368
getLineNumberFromChildDomNode(domNode: HTMLElement | null): number | null {
369
return this._getRenderedStickyLineFromChildDomNode(domNode)?.lineNumber ?? null;
370
}
371
372
private _getRenderedStickyLineFromChildDomNode(domNode: HTMLElement | null): RenderedStickyLine | null {
373
const index = this.getLineIndexFromChildDomNode(domNode);
374
if (index === null || index < 0 || index >= this._renderedStickyLines.length) {
375
return null;
376
}
377
return this._renderedStickyLines[index];
378
}
379
380
/**
381
* Given a child dom node, tries to find the line number attribute that was stored in the node.
382
* @returns the attribute value or null if none is found.
383
*/
384
getLineIndexFromChildDomNode(domNode: HTMLElement | null): number | null {
385
const lineIndex = this._getAttributeValue(domNode, STICKY_INDEX_ATTR);
386
return lineIndex ? parseInt(lineIndex, 10) : null;
387
}
388
389
/**
390
* Given a child dom node, tries to find if it is (contained in) a sticky line.
391
* @returns a boolean.
392
*/
393
isInStickyLine(domNode: HTMLElement | null): boolean {
394
const isInLine = this._getAttributeValue(domNode, STICKY_IS_LINE_ATTR);
395
return isInLine !== undefined;
396
}
397
398
/**
399
* Given a child dom node, tries to find if this dom node is (contained in) a sticky folding icon.
400
* @returns a boolean.
401
*/
402
isInFoldingIconDomNode(domNode: HTMLElement | null): boolean {
403
const isInFoldingIcon = this._getAttributeValue(domNode, STICKY_IS_FOLDING_ICON_ATTR);
404
return isInFoldingIcon !== undefined;
405
}
406
407
/**
408
* Given the dom node, finds if it or its parent sequence contains the given attribute.
409
* @returns the attribute value or undefined.
410
*/
411
private _getAttributeValue(domNode: HTMLElement | null, attribute: string): string | undefined {
412
while (domNode && domNode !== this._rootDomNode) {
413
const line = domNode.getAttribute(attribute);
414
if (line !== null) {
415
return line;
416
}
417
domNode = domNode.parentElement;
418
}
419
return;
420
}
421
}
422
423
class RenderedStickyLine {
424
425
public readonly lineDomNode: HTMLElement;
426
public readonly lineNumberDomNode: HTMLElement;
427
428
public readonly foldingIcon: StickyFoldingIcon | undefined;
429
public readonly characterMapping: CharacterMapping;
430
431
public readonly scrollWidth: number;
432
public readonly height: number;
433
434
constructor(
435
editor: ICodeEditor,
436
viewModel: IViewModel,
437
layoutInfo: EditorLayoutInfo,
438
foldingModel: FoldingModel | undefined,
439
isOnGlyphMargin: boolean,
440
public readonly index: number,
441
public readonly lineNumber: number,
442
) {
443
const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(lineNumber, 1)).lineNumber;
444
const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber);
445
const lineNumberOption = editor.getOption(EditorOption.lineNumbers);
446
const verticalScrollbarSize = editor.getOption(EditorOption.scrollbar).verticalScrollbarSize;
447
448
let actualInlineDecorations: LineDecoration[];
449
try {
450
actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
451
} catch (err) {
452
actualInlineDecorations = [];
453
}
454
455
const lineHeight = editor.getLineHeightForPosition(new Position(lineNumber, 1));
456
const textDirection = viewModel.getTextDirection(lineNumber);
457
const renderLineInput: RenderLineInput = new RenderLineInput(true, true, lineRenderingData.content,
458
lineRenderingData.continuesWithWrappedLine,
459
lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0,
460
lineRenderingData.tokens, actualInlineDecorations,
461
lineRenderingData.tabSize, lineRenderingData.startVisibleColumn,
462
1, 1, 1, 500, 'none', true, true, null,
463
textDirection, verticalScrollbarSize
464
);
465
466
const sb = new StringBuilder(2000);
467
const renderOutput = renderViewLine(renderLineInput, sb);
468
this.characterMapping = renderOutput.characterMapping;
469
470
let newLine;
471
if (_ttPolicy) {
472
newLine = _ttPolicy.createHTML(sb.build());
473
} else {
474
newLine = sb.build();
475
}
476
477
const lineHTMLNode = document.createElement('span');
478
lineHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
479
lineHTMLNode.setAttribute(STICKY_IS_LINE_ATTR, '');
480
lineHTMLNode.setAttribute('role', 'listitem');
481
lineHTMLNode.tabIndex = 0;
482
lineHTMLNode.className = 'sticky-line-content';
483
lineHTMLNode.classList.add(`stickyLine${lineNumber}`);
484
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
485
lineHTMLNode.innerHTML = newLine as string;
486
487
const lineNumberHTMLNode = document.createElement('span');
488
lineNumberHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index));
489
lineNumberHTMLNode.setAttribute(STICKY_IS_LINE_NUMBER_ATTR, '');
490
lineNumberHTMLNode.className = 'sticky-line-number';
491
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
492
const lineNumbersWidth = layoutInfo.contentLeft;
493
lineNumberHTMLNode.style.width = `${lineNumbersWidth}px`;
494
495
const innerLineNumberHTML = document.createElement('span');
496
if (lineNumberOption.renderType === RenderLineNumbersType.On || lineNumberOption.renderType === RenderLineNumbersType.Interval && lineNumber % 10 === 0) {
497
innerLineNumberHTML.innerText = lineNumber.toString();
498
} else if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {
499
innerLineNumberHTML.innerText = Math.abs(lineNumber - editor.getPosition()!.lineNumber).toString();
500
}
501
innerLineNumberHTML.className = 'sticky-line-number-inner';
502
innerLineNumberHTML.style.width = `${layoutInfo.lineNumbersWidth}px`;
503
innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft}px`;
504
505
lineNumberHTMLNode.appendChild(innerLineNumberHTML);
506
this.foldingIcon = this._renderFoldingIconForLine(editor, foldingModel, lineNumber, lineHeight, isOnGlyphMargin);
507
if (this.foldingIcon) {
508
lineNumberHTMLNode.appendChild(this.foldingIcon.domNode);
509
this.foldingIcon.domNode.style.left = `${layoutInfo.lineNumbersWidth + layoutInfo.lineNumbersLeft}px`;
510
this.foldingIcon.domNode.style.lineHeight = `${lineHeight}px`;
511
}
512
513
editor.applyFontInfo(lineHTMLNode);
514
editor.applyFontInfo(lineNumberHTMLNode);
515
516
lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
517
lineHTMLNode.style.lineHeight = `${lineHeight}px`;
518
lineNumberHTMLNode.style.height = `${lineHeight}px`;
519
lineHTMLNode.style.height = `${lineHeight}px`;
520
521
this.scrollWidth = lineHTMLNode.scrollWidth;
522
this.lineDomNode = lineHTMLNode;
523
this.lineNumberDomNode = lineNumberHTMLNode;
524
this.height = lineHeight;
525
}
526
527
private _renderFoldingIconForLine(editor: ICodeEditor, foldingModel: FoldingModel | undefined, line: number, lineHeight: number, isOnGlyphMargin: boolean): StickyFoldingIcon | undefined {
528
const showFoldingControls: 'mouseover' | 'always' | 'never' = editor.getOption(EditorOption.showFoldingControls);
529
if (!foldingModel || showFoldingControls === 'never') {
530
return;
531
}
532
const foldingRegions = foldingModel.regions;
533
const indexOfFoldingRegion = foldingRegions.findRange(line);
534
const startLineNumber = foldingRegions.getStartLineNumber(indexOfFoldingRegion);
535
const isFoldingScope = line === startLineNumber;
536
if (!isFoldingScope) {
537
return;
538
}
539
const isCollapsed = foldingRegions.isCollapsed(indexOfFoldingRegion);
540
const foldingIcon = new StickyFoldingIcon(isCollapsed, startLineNumber, foldingRegions.getEndLineNumber(indexOfFoldingRegion), lineHeight);
541
foldingIcon.setVisible(isOnGlyphMargin ? true : (isCollapsed || showFoldingControls === 'always'));
542
foldingIcon.domNode.setAttribute(STICKY_IS_FOLDING_ICON_ATTR, '');
543
return foldingIcon;
544
}
545
}
546
547
class StickyFoldingIcon {
548
549
public domNode: HTMLElement;
550
551
constructor(
552
public isCollapsed: boolean,
553
public foldingStartLine: number,
554
public foldingEndLine: number,
555
public dimension: number
556
) {
557
this.domNode = document.createElement('div');
558
this.domNode.style.width = `26px`;
559
this.domNode.style.height = `${dimension}px`;
560
this.domNode.style.lineHeight = `${dimension}px`;
561
this.domNode.className = ThemeIcon.asClassName(isCollapsed ? foldingCollapsedIcon : foldingExpandedIcon);
562
}
563
564
public setVisible(visible: boolean) {
565
this.domNode.style.cursor = visible ? 'pointer' : 'default';
566
this.domNode.style.opacity = visible ? '1' : '0';
567
}
568
}
569
570