Path: blob/main/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.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 './lineNumbers.css';6import * as platform from '../../../../base/common/platform.js';7import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';8import { RenderLineNumbersType, EditorOption } from '../../../common/config/editorOptions.js';9import { Position } from '../../../common/core/position.js';10import { Range } from '../../../common/core/range.js';11import { RenderingContext } from '../../view/renderingContext.js';12import { ViewContext } from '../../../common/viewModel/viewContext.js';13import * as viewEvents from '../../../common/viewEvents.js';14import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';15import { editorDimmedLineNumber, editorLineNumbers } from '../../../common/core/editorColorRegistry.js';1617/**18* Renders line numbers to the left of the main view lines content.19*/20export class LineNumbersOverlay extends DynamicViewOverlay {2122public static readonly CLASS_NAME = 'line-numbers';2324private readonly _context: ViewContext;2526private _lineHeight!: number;27private _renderLineNumbers!: RenderLineNumbersType;28private _renderCustomLineNumbers!: ((lineNumber: number) => string) | null;29private _renderFinalNewline!: 'off' | 'on' | 'dimmed';30private _lineNumbersLeft!: number;31private _lineNumbersWidth!: number;32private _lastCursorModelPosition: Position;33private _renderResult: string[] | null;34private _activeModelLineNumber: number;3536constructor(context: ViewContext) {37super();38this._context = context;3940this._readConfig();4142this._lastCursorModelPosition = new Position(1, 1);43this._renderResult = null;44this._activeModelLineNumber = 1;45this._context.addEventHandler(this);46}4748private _readConfig(): void {49const options = this._context.configuration.options;50this._lineHeight = options.get(EditorOption.lineHeight);51const lineNumbers = options.get(EditorOption.lineNumbers);52this._renderLineNumbers = lineNumbers.renderType;53this._renderCustomLineNumbers = lineNumbers.renderFn;54this._renderFinalNewline = options.get(EditorOption.renderFinalNewline);55const layoutInfo = options.get(EditorOption.layoutInfo);56this._lineNumbersLeft = layoutInfo.lineNumbersLeft;57this._lineNumbersWidth = layoutInfo.lineNumbersWidth;58}5960public override dispose(): void {61this._context.removeEventHandler(this);62this._renderResult = null;63super.dispose();64}6566// --- begin event handlers6768public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {69this._readConfig();70return true;71}72public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {73const primaryViewPosition = e.selections[0].getPosition();74this._lastCursorModelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition);7576let shouldRender = false;77if (this._activeModelLineNumber !== this._lastCursorModelPosition.lineNumber) {78this._activeModelLineNumber = this._lastCursorModelPosition.lineNumber;79shouldRender = true;80}81if (this._renderLineNumbers === RenderLineNumbersType.Relative || this._renderLineNumbers === RenderLineNumbersType.Interval) {82shouldRender = true;83}84return shouldRender;85}86public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {87return true;88}89public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {90return true;91}92public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {93return true;94}95public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {96return true;97}98public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {99return e.scrollTopChanged;100}101public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {102return true;103}104public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {105return e.affectsLineNumber;106}107108// --- end event handlers109110private _getLineRenderLineNumber(viewLineNumber: number): string {111const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(viewLineNumber, 1));112if (modelPosition.column !== 1) {113return '';114}115const modelLineNumber = modelPosition.lineNumber;116117if (this._renderCustomLineNumbers) {118return this._renderCustomLineNumbers(modelLineNumber);119}120121if (this._renderLineNumbers === RenderLineNumbersType.Relative) {122const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber);123if (diff === 0) {124return '<span class="relative-current-line-number">' + modelLineNumber + '</span>';125}126return String(diff);127}128129if (this._renderLineNumbers === RenderLineNumbersType.Interval) {130if (this._lastCursorModelPosition.lineNumber === modelLineNumber) {131return String(modelLineNumber);132}133if (modelLineNumber % 10 === 0) {134return String(modelLineNumber);135}136const finalLineNumber = this._context.viewModel.getLineCount();137if (modelLineNumber === finalLineNumber) {138return String(modelLineNumber);139}140return '';141}142143return String(modelLineNumber);144}145146public prepareRender(ctx: RenderingContext): void {147if (this._renderLineNumbers === RenderLineNumbersType.Off) {148this._renderResult = null;149return;150}151152const lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : '');153const visibleStartLineNumber = ctx.visibleRange.startLineNumber;154const visibleEndLineNumber = ctx.visibleRange.endLineNumber;155156const lineNoDecorations = this._context.viewModel.getDecorationsInViewport(ctx.visibleRange).filter(d => !!d.options.lineNumberClassName);157lineNoDecorations.sort((a, b) => Range.compareRangesUsingEnds(a.range, b.range));158let decorationStartIndex = 0;159160const lineCount = this._context.viewModel.getLineCount();161const output: string[] = [];162for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {163const lineIndex = lineNumber - visibleStartLineNumber;164const modelLineNumber: number = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)).lineNumber;165166let renderLineNumber = this._getLineRenderLineNumber(lineNumber);167let extraClassNames = '';168169// skip decorations whose end positions we've already passed170while (decorationStartIndex < lineNoDecorations.length && lineNoDecorations[decorationStartIndex].range.endLineNumber < lineNumber) {171decorationStartIndex++;172}173for (let i = decorationStartIndex; i < lineNoDecorations.length; i++) {174const { range, options } = lineNoDecorations[i];175if (range.startLineNumber <= lineNumber) {176extraClassNames += ' ' + options.lineNumberClassName;177}178}179180if (!renderLineNumber && !extraClassNames) {181output[lineIndex] = '';182continue;183}184185if (lineNumber === lineCount && this._context.viewModel.getLineLength(lineNumber) === 0) {186// this is the last line187if (this._renderFinalNewline === 'off') {188renderLineNumber = '';189}190if (this._renderFinalNewline === 'dimmed') {191extraClassNames += ' dimmed-line-number';192}193}194if (modelLineNumber === this._activeModelLineNumber) {195extraClassNames += ' active-line-number';196}197198199output[lineIndex] = (200`<div class="${LineNumbersOverlay.CLASS_NAME}${lineHeightClassName}${extraClassNames}" style="left:${this._lineNumbersLeft}px;width:${this._lineNumbersWidth}px;">${renderLineNumber}</div>`201);202}203204this._renderResult = output;205}206207public render(startLineNumber: number, lineNumber: number): string {208if (!this._renderResult) {209return '';210}211const lineIndex = lineNumber - startLineNumber;212if (lineIndex < 0 || lineIndex >= this._renderResult.length) {213return '';214}215return this._renderResult[lineIndex];216}217}218219registerThemingParticipant((theme, collector) => {220const editorLineNumbersColor = theme.getColor(editorLineNumbers);221const editorDimmedLineNumberColor = theme.getColor(editorDimmedLineNumber);222if (editorDimmedLineNumberColor) {223collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorDimmedLineNumberColor}; }`);224} else if (editorLineNumbersColor) {225collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorLineNumbersColor.transparent(0.4)}; }`);226}227});228229230