monaco-editor
Version:
A browser based code editor
211 lines (210 loc) • 9.79 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../../base/browser/dom.js';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import * as strings from '../../../../base/common/strings.js';
import { applyFontInfo } from '../../config/domFontInfo.js';
import { TextEditorCursorStyle } from '../../../common/config/editorOptions.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';
class ViewCursorRenderData {
constructor(top, left, paddingLeft, width, height, textContent, textContentClassName) {
this.top = top;
this.left = left;
this.paddingLeft = paddingLeft;
this.width = width;
this.height = height;
this.textContent = textContent;
this.textContentClassName = textContentClassName;
}
}
export var CursorPlurality;
(function (CursorPlurality) {
CursorPlurality[CursorPlurality["Single"] = 0] = "Single";
CursorPlurality[CursorPlurality["MultiPrimary"] = 1] = "MultiPrimary";
CursorPlurality[CursorPlurality["MultiSecondary"] = 2] = "MultiSecondary";
})(CursorPlurality || (CursorPlurality = {}));
export class ViewCursor {
constructor(context, plurality) {
this._context = context;
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._cursorStyle = options.get(28 /* EditorOption.cursorStyle */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._lineCursorWidth = Math.min(options.get(31 /* EditorOption.cursorWidth */), this._typicalHalfwidthCharacterWidth);
this._isVisible = true;
// Create the dom node
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setClassName(`cursor ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
this._domNode.setHeight(this._lineHeight);
this._domNode.setTop(0);
this._domNode.setLeft(0);
applyFontInfo(this._domNode, fontInfo);
this._domNode.setDisplay('none');
this._position = new Position(1, 1);
this._pluralityClass = '';
this.setPlurality(plurality);
this._lastRenderedContent = '';
this._renderData = null;
}
getDomNode() {
return this._domNode;
}
getPosition() {
return this._position;
}
setPlurality(plurality) {
switch (plurality) {
default:
case CursorPlurality.Single:
this._pluralityClass = '';
break;
case CursorPlurality.MultiPrimary:
this._pluralityClass = 'cursor-primary';
break;
case CursorPlurality.MultiSecondary:
this._pluralityClass = 'cursor-secondary';
break;
}
}
show() {
if (!this._isVisible) {
this._domNode.setVisibility('inherit');
this._isVisible = true;
}
}
hide() {
if (this._isVisible) {
this._domNode.setVisibility('hidden');
this._isVisible = false;
}
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._cursorStyle = options.get(28 /* EditorOption.cursorStyle */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._lineCursorWidth = Math.min(options.get(31 /* EditorOption.cursorWidth */), this._typicalHalfwidthCharacterWidth);
applyFontInfo(this._domNode, fontInfo);
return true;
}
onCursorPositionChanged(position, pauseAnimation) {
if (pauseAnimation) {
this._domNode.domNode.style.transitionProperty = 'none';
}
else {
this._domNode.domNode.style.transitionProperty = '';
}
this._position = position;
return true;
}
/**
* If `this._position` is inside a grapheme, returns the position where the grapheme starts.
* Also returns the next grapheme.
*/
_getGraphemeAwarePosition() {
const { lineNumber, column } = this._position;
const lineContent = this._context.viewModel.getLineContent(lineNumber);
const [startOffset, endOffset] = strings.getCharContainingOffset(lineContent, column - 1);
return [new Position(lineNumber, startOffset + 1), lineContent.substring(startOffset, endOffset)];
}
_prepareRender(ctx) {
let textContent = '';
let textContentClassName = '';
const [position, nextGrapheme] = this._getGraphemeAwarePosition();
if (this._cursorStyle === TextEditorCursorStyle.Line || this._cursorStyle === TextEditorCursorStyle.LineThin) {
const visibleRange = ctx.visibleRangeForPosition(position);
if (!visibleRange || visibleRange.outsideRenderedLine) {
// Outside viewport
return null;
}
const window = dom.getWindow(this._domNode.domNode);
let width;
if (this._cursorStyle === TextEditorCursorStyle.Line) {
width = dom.computeScreenAwareSize(window, this._lineCursorWidth > 0 ? this._lineCursorWidth : 2);
if (width > 2) {
textContent = nextGrapheme;
textContentClassName = this._getTokenClassName(position);
}
}
else {
width = dom.computeScreenAwareSize(window, 1);
}
let left = visibleRange.left;
let paddingLeft = 0;
if (width >= 2 && left >= 1) {
// shift the cursor a bit between the characters
paddingLeft = 1;
left -= paddingLeft;
}
const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta;
return new ViewCursorRenderData(top, left, paddingLeft, width, this._lineHeight, textContent, textContentClassName);
}
const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(position.lineNumber, position.column, position.lineNumber, position.column + nextGrapheme.length), false);
if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0) {
// Outside viewport
return null;
}
const firstVisibleRangeForCharacter = visibleRangeForCharacter[0];
if (firstVisibleRangeForCharacter.outsideRenderedLine || firstVisibleRangeForCharacter.ranges.length === 0) {
// Outside viewport
return null;
}
const range = firstVisibleRangeForCharacter.ranges[0];
const width = (nextGrapheme === '\t'
? this._typicalHalfwidthCharacterWidth
: (range.width < 1
? this._typicalHalfwidthCharacterWidth
: range.width));
if (this._cursorStyle === TextEditorCursorStyle.Block) {
textContent = nextGrapheme;
textContentClassName = this._getTokenClassName(position);
}
let top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.bigNumbersDelta;
let height = this._lineHeight;
// Underline might interfere with clicking
if (this._cursorStyle === TextEditorCursorStyle.Underline || this._cursorStyle === TextEditorCursorStyle.UnderlineThin) {
top += this._lineHeight - 2;
height = 2;
}
return new ViewCursorRenderData(top, range.left, 0, width, height, textContent, textContentClassName);
}
_getTokenClassName(position) {
const lineData = this._context.viewModel.getViewLineData(position.lineNumber);
const tokenIndex = lineData.tokens.findTokenIndexAtOffset(position.column - 1);
return lineData.tokens.getClassName(tokenIndex);
}
prepareRender(ctx) {
this._renderData = this._prepareRender(ctx);
}
render(ctx) {
if (!this._renderData) {
this._domNode.setDisplay('none');
return null;
}
if (this._lastRenderedContent !== this._renderData.textContent) {
this._lastRenderedContent = this._renderData.textContent;
this._domNode.domNode.textContent = this._lastRenderedContent;
}
this._domNode.setClassName(`cursor ${this._pluralityClass} ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`);
this._domNode.setDisplay('block');
this._domNode.setTop(this._renderData.top);
this._domNode.setLeft(this._renderData.left);
this._domNode.setPaddingLeft(this._renderData.paddingLeft);
this._domNode.setWidth(this._renderData.width);
this._domNode.setLineHeight(this._renderData.height);
this._domNode.setHeight(this._renderData.height);
return {
domNode: this._domNode.domNode,
position: this._position,
contentLeft: this._renderData.left,
height: this._renderData.height,
width: 2
};
}
}