Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.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 './lineNumbers.css';
7
import * as platform from '../../../../base/common/platform.js';
8
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
9
import { RenderLineNumbersType, EditorOption } from '../../../common/config/editorOptions.js';
10
import { Position } from '../../../common/core/position.js';
11
import { Range } from '../../../common/core/range.js';
12
import { RenderingContext } from '../../view/renderingContext.js';
13
import { ViewContext } from '../../../common/viewModel/viewContext.js';
14
import * as viewEvents from '../../../common/viewEvents.js';
15
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
16
import { editorDimmedLineNumber, editorLineNumbers } from '../../../common/core/editorColorRegistry.js';
17
18
/**
19
* Renders line numbers to the left of the main view lines content.
20
*/
21
export class LineNumbersOverlay extends DynamicViewOverlay {
22
23
public static readonly CLASS_NAME = 'line-numbers';
24
25
private readonly _context: ViewContext;
26
27
private _lineHeight!: number;
28
private _renderLineNumbers!: RenderLineNumbersType;
29
private _renderCustomLineNumbers!: ((lineNumber: number) => string) | null;
30
private _renderFinalNewline!: 'off' | 'on' | 'dimmed';
31
private _lineNumbersLeft!: number;
32
private _lineNumbersWidth!: number;
33
private _lastCursorModelPosition: Position;
34
private _renderResult: string[] | null;
35
private _activeModelLineNumber: number;
36
37
constructor(context: ViewContext) {
38
super();
39
this._context = context;
40
41
this._readConfig();
42
43
this._lastCursorModelPosition = new Position(1, 1);
44
this._renderResult = null;
45
this._activeModelLineNumber = 1;
46
this._context.addEventHandler(this);
47
}
48
49
private _readConfig(): void {
50
const options = this._context.configuration.options;
51
this._lineHeight = options.get(EditorOption.lineHeight);
52
const lineNumbers = options.get(EditorOption.lineNumbers);
53
this._renderLineNumbers = lineNumbers.renderType;
54
this._renderCustomLineNumbers = lineNumbers.renderFn;
55
this._renderFinalNewline = options.get(EditorOption.renderFinalNewline);
56
const layoutInfo = options.get(EditorOption.layoutInfo);
57
this._lineNumbersLeft = layoutInfo.lineNumbersLeft;
58
this._lineNumbersWidth = layoutInfo.lineNumbersWidth;
59
}
60
61
public override dispose(): void {
62
this._context.removeEventHandler(this);
63
this._renderResult = null;
64
super.dispose();
65
}
66
67
// --- begin event handlers
68
69
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
70
this._readConfig();
71
return true;
72
}
73
public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
74
const primaryViewPosition = e.selections[0].getPosition();
75
this._lastCursorModelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition);
76
77
let shouldRender = false;
78
if (this._activeModelLineNumber !== this._lastCursorModelPosition.lineNumber) {
79
this._activeModelLineNumber = this._lastCursorModelPosition.lineNumber;
80
shouldRender = true;
81
}
82
if (this._renderLineNumbers === RenderLineNumbersType.Relative || this._renderLineNumbers === RenderLineNumbersType.Interval) {
83
shouldRender = true;
84
}
85
return shouldRender;
86
}
87
public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
88
return true;
89
}
90
public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
91
return true;
92
}
93
public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
94
return true;
95
}
96
public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
97
return true;
98
}
99
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
100
return e.scrollTopChanged;
101
}
102
public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
103
return true;
104
}
105
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
106
return e.affectsLineNumber;
107
}
108
109
// --- end event handlers
110
111
private _getLineRenderLineNumber(viewLineNumber: number): string {
112
const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(viewLineNumber, 1));
113
if (modelPosition.column !== 1) {
114
return '';
115
}
116
const modelLineNumber = modelPosition.lineNumber;
117
118
if (this._renderCustomLineNumbers) {
119
return this._renderCustomLineNumbers(modelLineNumber);
120
}
121
122
if (this._renderLineNumbers === RenderLineNumbersType.Relative) {
123
const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber);
124
if (diff === 0) {
125
return '<span class="relative-current-line-number">' + modelLineNumber + '</span>';
126
}
127
return String(diff);
128
}
129
130
if (this._renderLineNumbers === RenderLineNumbersType.Interval) {
131
if (this._lastCursorModelPosition.lineNumber === modelLineNumber) {
132
return String(modelLineNumber);
133
}
134
if (modelLineNumber % 10 === 0) {
135
return String(modelLineNumber);
136
}
137
const finalLineNumber = this._context.viewModel.getLineCount();
138
if (modelLineNumber === finalLineNumber) {
139
return String(modelLineNumber);
140
}
141
return '';
142
}
143
144
return String(modelLineNumber);
145
}
146
147
public prepareRender(ctx: RenderingContext): void {
148
if (this._renderLineNumbers === RenderLineNumbersType.Off) {
149
this._renderResult = null;
150
return;
151
}
152
153
const lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : '');
154
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
155
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
156
157
const lineNoDecorations = this._context.viewModel.getDecorationsInViewport(ctx.visibleRange).filter(d => !!d.options.lineNumberClassName);
158
lineNoDecorations.sort((a, b) => Range.compareRangesUsingEnds(a.range, b.range));
159
let decorationStartIndex = 0;
160
161
const lineCount = this._context.viewModel.getLineCount();
162
const output: string[] = [];
163
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
164
const lineIndex = lineNumber - visibleStartLineNumber;
165
const modelLineNumber: number = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)).lineNumber;
166
167
let renderLineNumber = this._getLineRenderLineNumber(lineNumber);
168
let extraClassNames = '';
169
170
// skip decorations whose end positions we've already passed
171
while (decorationStartIndex < lineNoDecorations.length && lineNoDecorations[decorationStartIndex].range.endLineNumber < lineNumber) {
172
decorationStartIndex++;
173
}
174
for (let i = decorationStartIndex; i < lineNoDecorations.length; i++) {
175
const { range, options } = lineNoDecorations[i];
176
if (range.startLineNumber <= lineNumber) {
177
extraClassNames += ' ' + options.lineNumberClassName;
178
}
179
}
180
181
if (!renderLineNumber && !extraClassNames) {
182
output[lineIndex] = '';
183
continue;
184
}
185
186
if (lineNumber === lineCount && this._context.viewModel.getLineLength(lineNumber) === 0) {
187
// this is the last line
188
if (this._renderFinalNewline === 'off') {
189
renderLineNumber = '';
190
}
191
if (this._renderFinalNewline === 'dimmed') {
192
extraClassNames += ' dimmed-line-number';
193
}
194
}
195
if (modelLineNumber === this._activeModelLineNumber) {
196
extraClassNames += ' active-line-number';
197
}
198
199
200
output[lineIndex] = (
201
`<div class="${LineNumbersOverlay.CLASS_NAME}${lineHeightClassName}${extraClassNames}" style="left:${this._lineNumbersLeft}px;width:${this._lineNumbersWidth}px;">${renderLineNumber}</div>`
202
);
203
}
204
205
this._renderResult = output;
206
}
207
208
public render(startLineNumber: number, lineNumber: number): string {
209
if (!this._renderResult) {
210
return '';
211
}
212
const lineIndex = lineNumber - startLineNumber;
213
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
214
return '';
215
}
216
return this._renderResult[lineIndex];
217
}
218
}
219
220
registerThemingParticipant((theme, collector) => {
221
const editorLineNumbersColor = theme.getColor(editorLineNumbers);
222
const editorDimmedLineNumberColor = theme.getColor(editorDimmedLineNumber);
223
if (editorDimmedLineNumberColor) {
224
collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorDimmedLineNumberColor}; }`);
225
} else if (editorLineNumbersColor) {
226
collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorLineNumbersColor.transparent(0.4)}; }`);
227
}
228
});
229
230