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