feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { applyFontInfo } from './domFontInfo.js';
export class CharWidthRequest {
constructor(chr, type) {
this.chr = chr;
this.type = type;
this.width = 0;
}
fulfill(width) {
this.width = width;
}
}
class DomCharWidthReader {
constructor(bareFontInfo, requests) {
this._bareFontInfo = bareFontInfo;
this._requests = requests;
this._container = null;
this._testElements = null;
}
read(targetWindow) {
// Create a test container with all these test elements
this._createDomElements();
// Add the container to the DOM
targetWindow.document.body.appendChild(this._container);
// Read character widths
this._readFromDomElements();
// Remove the container from the DOM
this._container?.remove();
this._container = null;
this._testElements = null;
}
_createDomElements() {
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '-50000px';
container.style.width = '50000px';
const regularDomNode = document.createElement('div');
applyFontInfo(regularDomNode, this._bareFontInfo);
container.appendChild(regularDomNode);
const boldDomNode = document.createElement('div');
applyFontInfo(boldDomNode, this._bareFontInfo);
boldDomNode.style.fontWeight = 'bold';
container.appendChild(boldDomNode);
const italicDomNode = document.createElement('div');
applyFontInfo(italicDomNode, this._bareFontInfo);
italicDomNode.style.fontStyle = 'italic';
container.appendChild(italicDomNode);
const testElements = [];
for (const request of this._requests) {
let parent;
if (request.type === 0 /* CharWidthRequestType.Regular */) {
parent = regularDomNode;
}
if (request.type === 2 /* CharWidthRequestType.Bold */) {
parent = boldDomNode;
}
if (request.type === 1 /* CharWidthRequestType.Italic */) {
parent = italicDomNode;
}
parent.appendChild(document.createElement('br'));
const testElement = document.createElement('span');
DomCharWidthReader._render(testElement, request);
parent.appendChild(testElement);
testElements.push(testElement);
}
this._container = container;
this._testElements = testElements;
}
static _render(testElement, request) {
if (request.chr === ' ') {
let htmlString = '\u00a0';
// Repeat character 256 (2^8) times
for (let i = 0; i < 8; i++) {
htmlString += htmlString;
}
testElement.innerText = htmlString;
}
else {
let testString = request.chr;
// Repeat character 256 (2^8) times
for (let i = 0; i < 8; i++) {
testString += testString;
}
testElement.textContent = testString;
}
}
_readFromDomElements() {
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
const testElement = this._testElements[i];
request.fulfill(testElement.offsetWidth / 256);
}
}
}
export function readCharWidths(targetWindow, bareFontInfo, requests) {
const reader = new DomCharWidthReader(bareFontInfo, requests);
reader.read(targetWindow);
}
@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FastDomNode } from '../../../base/browser/fastDomNode.js';
export function applyFontInfo(domNode, fontInfo) {
if (domNode instanceof FastDomNode) {
domNode.setFontFamily(fontInfo.getMassagedFontFamily());
domNode.setFontWeight(fontInfo.fontWeight);
domNode.setFontSize(fontInfo.fontSize);
domNode.setFontFeatureSettings(fontInfo.fontFeatureSettings);
domNode.setFontVariationSettings(fontInfo.fontVariationSettings);
domNode.setLineHeight(fontInfo.lineHeight);
domNode.setLetterSpacing(fontInfo.letterSpacing);
}
else {
domNode.style.fontFamily = fontInfo.getMassagedFontFamily();
domNode.style.fontWeight = fontInfo.fontWeight;
domNode.style.fontSize = fontInfo.fontSize + 'px';
domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
domNode.style.fontVariationSettings = fontInfo.fontVariationSettings;
domNode.style.lineHeight = fontInfo.lineHeight + 'px';
domNode.style.letterSpacing = fontInfo.letterSpacing + 'px';
}
}
@@ -0,0 +1,284 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as browser from '../../../base/browser/browser.js';
import * as arrays from '../../../base/common/arrays.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import * as objects from '../../../base/common/objects.js';
import * as platform from '../../../base/common/platform.js';
import { ElementSizeObserver } from './elementSizeObserver.js';
import { FontMeasurements } from './fontMeasurements.js';
import { migrateOptions } from './migrateOptions.js';
import { TabFocus } from './tabFocus.js';
import { ComputeOptionsMemory, ConfigurationChangedEvent, editorOptionsRegistry } from '../../common/config/editorOptions.js';
import { EditorZoom } from '../../common/config/editorZoom.js';
import { BareFontInfo } from '../../common/config/fontInfo.js';
import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';
import { getWindow, getWindowById } from '../../../base/browser/dom.js';
import { PixelRatio } from '../../../base/browser/pixelRatio.js';
let EditorConfiguration = class EditorConfiguration extends Disposable {
constructor(isSimpleWidget, contextMenuId, options, container, _accessibilityService) {
super();
this._accessibilityService = _accessibilityService;
this._onDidChange = this._register(new Emitter());
this.onDidChange = this._onDidChange.event;
this._onDidChangeFast = this._register(new Emitter());
this.onDidChangeFast = this._onDidChangeFast.event;
this._isDominatedByLongLines = false;
this._viewLineCount = 1;
this._lineNumbersDigitCount = 1;
this._reservedHeight = 0;
this._glyphMarginDecorationLaneCount = 1;
this._computeOptionsMemory = new ComputeOptionsMemory();
this.isSimpleWidget = isSimpleWidget;
this.contextMenuId = contextMenuId;
this._containerObserver = this._register(new ElementSizeObserver(container, options.dimension));
this._targetWindowId = getWindow(container).vscodeWindowId;
this._rawOptions = deepCloneAndMigrateOptions(options);
this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);
this.options = this._computeOptions();
if (this.options.get(13 /* EditorOption.automaticLayout */)) {
this._containerObserver.startObserving();
}
this._register(EditorZoom.onDidChangeZoomLevel(() => this._recomputeOptions()));
this._register(TabFocus.onDidChangeTabFocus(() => this._recomputeOptions()));
this._register(this._containerObserver.onDidChange(() => this._recomputeOptions()));
this._register(FontMeasurements.onDidChange(() => this._recomputeOptions()));
this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions()));
this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions()));
}
_recomputeOptions() {
const newOptions = this._computeOptions();
const changeEvent = EditorOptionsUtil.checkEquals(this.options, newOptions);
if (changeEvent === null) {
// nothing changed!
return;
}
this.options = newOptions;
this._onDidChangeFast.fire(changeEvent);
this._onDidChange.fire(changeEvent);
}
_computeOptions() {
const partialEnv = this._readEnvConfiguration();
const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget);
const fontInfo = this._readFontInfo(bareFontInfo);
const env = {
memory: this._computeOptionsMemory,
outerWidth: partialEnv.outerWidth,
outerHeight: partialEnv.outerHeight - this._reservedHeight,
fontInfo: fontInfo,
extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines,
viewLineCount: this._viewLineCount,
lineNumbersDigitCount: this._lineNumbersDigitCount,
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio,
tabFocusMode: TabFocus.getTabFocusMode(),
accessibilitySupport: partialEnv.accessibilitySupport,
glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount
};
return EditorOptionsUtil.computeOptions(this._validatedOptions, env);
}
_readEnvConfiguration() {
return {
extraEditorClassName: getExtraEditorClassName(),
outerWidth: this._containerObserver.getWidth(),
outerHeight: this._containerObserver.getHeight(),
emptySelectionClipboard: browser.isWebKit || browser.isFirefox,
pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value,
accessibilitySupport: (this._accessibilityService.isScreenReaderOptimized()
? 2 /* AccessibilitySupport.Enabled */
: this._accessibilityService.getAccessibilitySupport())
};
}
_readFontInfo(bareFontInfo) {
return FontMeasurements.readFontInfo(getWindowById(this._targetWindowId, true).window, bareFontInfo);
}
getRawOptions() {
return this._rawOptions;
}
updateOptions(_newOptions) {
const newOptions = deepCloneAndMigrateOptions(_newOptions);
const didChange = EditorOptionsUtil.applyUpdate(this._rawOptions, newOptions);
if (!didChange) {
return;
}
this._validatedOptions = EditorOptionsUtil.validateOptions(this._rawOptions);
this._recomputeOptions();
}
observeContainer(dimension) {
this._containerObserver.observe(dimension);
}
setIsDominatedByLongLines(isDominatedByLongLines) {
if (this._isDominatedByLongLines === isDominatedByLongLines) {
return;
}
this._isDominatedByLongLines = isDominatedByLongLines;
this._recomputeOptions();
}
setModelLineCount(modelLineCount) {
const lineNumbersDigitCount = digitCount(modelLineCount);
if (this._lineNumbersDigitCount === lineNumbersDigitCount) {
return;
}
this._lineNumbersDigitCount = lineNumbersDigitCount;
this._recomputeOptions();
}
setViewLineCount(viewLineCount) {
if (this._viewLineCount === viewLineCount) {
return;
}
this._viewLineCount = viewLineCount;
this._recomputeOptions();
}
setReservedHeight(reservedHeight) {
if (this._reservedHeight === reservedHeight) {
return;
}
this._reservedHeight = reservedHeight;
this._recomputeOptions();
}
setGlyphMarginDecorationLaneCount(decorationLaneCount) {
if (this._glyphMarginDecorationLaneCount === decorationLaneCount) {
return;
}
this._glyphMarginDecorationLaneCount = decorationLaneCount;
this._recomputeOptions();
}
};
EditorConfiguration = __decorate([
__param(4, IAccessibilityService)
], EditorConfiguration);
export { EditorConfiguration };
function digitCount(n) {
let r = 0;
while (n) {
n = Math.floor(n / 10);
r++;
}
return r ? r : 1;
}
function getExtraEditorClassName() {
let extra = '';
if (!browser.isSafari && !browser.isWebkitWebView) {
// Use user-select: none in all browsers except Safari and native macOS WebView
extra += 'no-user-select ';
}
if (browser.isSafari) {
// See https://github.com/microsoft/vscode/issues/108822
extra += 'no-minimap-shadow ';
extra += 'enable-user-select ';
}
if (platform.isMacintosh) {
extra += 'mac ';
}
return extra;
}
class ValidatedEditorOptions {
constructor() {
this._values = [];
}
_read(option) {
return this._values[option];
}
get(id) {
return this._values[id];
}
_write(option, value) {
this._values[option] = value;
}
}
export class ComputedEditorOptions {
constructor() {
this._values = [];
}
_read(id) {
if (id >= this._values.length) {
throw new Error('Cannot read uninitialized value');
}
return this._values[id];
}
get(id) {
return this._read(id);
}
_write(id, value) {
this._values[id] = value;
}
}
class EditorOptionsUtil {
static validateOptions(options) {
const result = new ValidatedEditorOptions();
for (const editorOption of editorOptionsRegistry) {
const value = (editorOption.name === '_never_' ? undefined : options[editorOption.name]);
result._write(editorOption.id, editorOption.validate(value));
}
return result;
}
static computeOptions(options, env) {
const result = new ComputedEditorOptions();
for (const editorOption of editorOptionsRegistry) {
result._write(editorOption.id, editorOption.compute(env, result, options._read(editorOption.id)));
}
return result;
}
static _deepEquals(a, b) {
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
return a === b;
}
if (Array.isArray(a) || Array.isArray(b)) {
return (Array.isArray(a) && Array.isArray(b) ? arrays.equals(a, b) : false);
}
if (Object.keys(a).length !== Object.keys(b).length) {
return false;
}
for (const key in a) {
if (!EditorOptionsUtil._deepEquals(a[key], b[key])) {
return false;
}
}
return true;
}
static checkEquals(a, b) {
const result = [];
let somethingChanged = false;
for (const editorOption of editorOptionsRegistry) {
const changed = !EditorOptionsUtil._deepEquals(a._read(editorOption.id), b._read(editorOption.id));
result[editorOption.id] = changed;
if (changed) {
somethingChanged = true;
}
}
return (somethingChanged ? new ConfigurationChangedEvent(result) : null);
}
/**
* Returns true if something changed.
* Modifies `options`.
*/
static applyUpdate(options, update) {
let changed = false;
for (const editorOption of editorOptionsRegistry) {
if (update.hasOwnProperty(editorOption.name)) {
const result = editorOption.applyUpdate(options[editorOption.name], update[editorOption.name]);
options[editorOption.name] = result.newValue;
changed = changed || result.didChange;
}
}
return changed;
}
}
function deepCloneAndMigrateOptions(_options) {
const options = objects.deepClone(_options);
migrateOptions(options);
return options;
}
@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
import { Emitter } from '../../../base/common/event.js';
import { getWindow, scheduleAtNextAnimationFrame } from '../../../base/browser/dom.js';
export class ElementSizeObserver extends Disposable {
constructor(referenceDomElement, dimension) {
super();
this._onDidChange = this._register(new Emitter());
this.onDidChange = this._onDidChange.event;
this._referenceDomElement = referenceDomElement;
this._width = -1;
this._height = -1;
this._resizeObserver = null;
this.measureReferenceDomElement(false, dimension);
}
dispose() {
this.stopObserving();
super.dispose();
}
getWidth() {
return this._width;
}
getHeight() {
return this._height;
}
startObserving() {
if (!this._resizeObserver && this._referenceDomElement) {
// We want to react to the resize observer only once per animation frame
// The first time the resize observer fires, we will react to it immediately.
// Otherwise we will postpone to the next animation frame.
// We'll use `observeContentRect` to store the content rect we received.
let observedDimenstion = null;
const observeNow = () => {
if (observedDimenstion) {
this.observe({ width: observedDimenstion.width, height: observedDimenstion.height });
}
else {
this.observe();
}
};
let shouldObserve = false;
let alreadyObservedThisAnimationFrame = false;
const update = () => {
if (shouldObserve && !alreadyObservedThisAnimationFrame) {
try {
shouldObserve = false;
alreadyObservedThisAnimationFrame = true;
observeNow();
}
finally {
scheduleAtNextAnimationFrame(getWindow(this._referenceDomElement), () => {
alreadyObservedThisAnimationFrame = false;
update();
});
}
}
};
this._resizeObserver = new ResizeObserver((entries) => {
if (entries && entries[0] && entries[0].contentRect) {
observedDimenstion = { width: entries[0].contentRect.width, height: entries[0].contentRect.height };
}
else {
observedDimenstion = null;
}
shouldObserve = true;
update();
});
this._resizeObserver.observe(this._referenceDomElement);
}
}
stopObserving() {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
this._resizeObserver = null;
}
}
observe(dimension) {
this.measureReferenceDomElement(true, dimension);
}
measureReferenceDomElement(emitEvent, dimension) {
let observedWidth = 0;
let observedHeight = 0;
if (dimension) {
observedWidth = dimension.width;
observedHeight = dimension.height;
}
else if (this._referenceDomElement) {
observedWidth = this._referenceDomElement.clientWidth;
observedHeight = this._referenceDomElement.clientHeight;
}
observedWidth = Math.max(5, observedWidth);
observedHeight = Math.max(5, observedHeight);
if (this._width !== observedWidth || this._height !== observedHeight) {
this._width = observedWidth;
this._height = observedHeight;
if (emitEvent) {
this._onDidChange.fire();
}
}
}
}
@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getWindowId } from '../../../base/browser/dom.js';
import { PixelRatio } from '../../../base/browser/pixelRatio.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { CharWidthRequest, readCharWidths } from './charWidthReader.js';
import { EditorFontLigatures } from '../../common/config/editorOptions.js';
import { FontInfo } from '../../common/config/fontInfo.js';
export class FontMeasurementsImpl extends Disposable {
constructor() {
super(...arguments);
this._cache = new Map();
this._evictUntrustedReadingsTimeout = -1;
this._onDidChange = this._register(new Emitter());
this.onDidChange = this._onDidChange.event;
}
dispose() {
if (this._evictUntrustedReadingsTimeout !== -1) {
clearTimeout(this._evictUntrustedReadingsTimeout);
this._evictUntrustedReadingsTimeout = -1;
}
super.dispose();
}
/**
* Clear all cached font information and trigger a change event.
*/
clearAllFontInfos() {
this._cache.clear();
this._onDidChange.fire();
}
_ensureCache(targetWindow) {
const windowId = getWindowId(targetWindow);
let cache = this._cache.get(windowId);
if (!cache) {
cache = new FontMeasurementsCache();
this._cache.set(windowId, cache);
}
return cache;
}
_writeToCache(targetWindow, item, value) {
const cache = this._ensureCache(targetWindow);
cache.put(item, value);
if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) {
// Try reading again after some time
this._evictUntrustedReadingsTimeout = targetWindow.setTimeout(() => {
this._evictUntrustedReadingsTimeout = -1;
this._evictUntrustedReadings(targetWindow);
}, 5000);
}
}
_evictUntrustedReadings(targetWindow) {
const cache = this._ensureCache(targetWindow);
const values = cache.getValues();
let somethingRemoved = false;
for (const item of values) {
if (!item.isTrusted) {
somethingRemoved = true;
cache.remove(item);
}
}
if (somethingRemoved) {
this._onDidChange.fire();
}
}
/**
* Read font information.
*/
readFontInfo(targetWindow, bareFontInfo) {
const cache = this._ensureCache(targetWindow);
if (!cache.has(bareFontInfo)) {
let readConfig = this._actualReadFontInfo(targetWindow, bareFontInfo);
if (readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.spaceWidth <= 2 || readConfig.maxDigitWidth <= 2) {
// Hey, it's Bug 14341 ... we couldn't read
readConfig = new FontInfo({
pixelRatio: PixelRatio.getInstance(targetWindow).value,
fontFamily: readConfig.fontFamily,
fontWeight: readConfig.fontWeight,
fontSize: readConfig.fontSize,
fontFeatureSettings: readConfig.fontFeatureSettings,
fontVariationSettings: readConfig.fontVariationSettings,
lineHeight: readConfig.lineHeight,
letterSpacing: readConfig.letterSpacing,
isMonospace: readConfig.isMonospace,
typicalHalfwidthCharacterWidth: Math.max(readConfig.typicalHalfwidthCharacterWidth, 5),
typicalFullwidthCharacterWidth: Math.max(readConfig.typicalFullwidthCharacterWidth, 5),
canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow,
spaceWidth: Math.max(readConfig.spaceWidth, 5),
middotWidth: Math.max(readConfig.middotWidth, 5),
wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5),
maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5),
}, false);
}
this._writeToCache(targetWindow, bareFontInfo, readConfig);
}
return cache.get(bareFontInfo);
}
_createRequest(chr, type, all, monospace) {
const result = new CharWidthRequest(chr, type);
all.push(result);
monospace?.push(result);
return result;
}
_actualReadFontInfo(targetWindow, bareFontInfo) {
const all = [];
const monospace = [];
const typicalHalfwidthCharacter = this._createRequest('n', 0 /* CharWidthRequestType.Regular */, all, monospace);
const typicalFullwidthCharacter = this._createRequest('\uff4d', 0 /* CharWidthRequestType.Regular */, all, null);
const space = this._createRequest(' ', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit0 = this._createRequest('0', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit1 = this._createRequest('1', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit2 = this._createRequest('2', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit3 = this._createRequest('3', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit4 = this._createRequest('4', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit5 = this._createRequest('5', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit6 = this._createRequest('6', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit7 = this._createRequest('7', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit8 = this._createRequest('8', 0 /* CharWidthRequestType.Regular */, all, monospace);
const digit9 = this._createRequest('9', 0 /* CharWidthRequestType.Regular */, all, monospace);
// monospace test: used for whitespace rendering
const rightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, monospace);
const halfwidthRightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, null);
// U+00B7 - MIDDLE DOT
const middot = this._createRequest('·', 0 /* CharWidthRequestType.Regular */, all, monospace);
// U+2E31 - WORD SEPARATOR MIDDLE DOT
const wsmiddotWidth = this._createRequest(String.fromCharCode(0x2E31), 0 /* CharWidthRequestType.Regular */, all, null);
// monospace test: some characters
const monospaceTestChars = '|/-_ilm%';
for (let i = 0, len = monospaceTestChars.length; i < len; i++) {
this._createRequest(monospaceTestChars.charAt(i), 0 /* CharWidthRequestType.Regular */, all, monospace);
this._createRequest(monospaceTestChars.charAt(i), 1 /* CharWidthRequestType.Italic */, all, monospace);
this._createRequest(monospaceTestChars.charAt(i), 2 /* CharWidthRequestType.Bold */, all, monospace);
}
readCharWidths(targetWindow, bareFontInfo, all);
const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width);
let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF);
const referenceWidth = monospace[0].width;
for (let i = 1, len = monospace.length; isMonospace && i < len; i++) {
const diff = referenceWidth - monospace[i].width;
if (diff < -0.001 || diff > 0.001) {
isMonospace = false;
break;
}
}
let canUseHalfwidthRightwardsArrow = true;
if (isMonospace && halfwidthRightwardsArrow.width !== referenceWidth) {
// using a halfwidth rightwards arrow would break monospace...
canUseHalfwidthRightwardsArrow = false;
}
if (halfwidthRightwardsArrow.width > rightwardsArrow.width) {
// using a halfwidth rightwards arrow would paint a larger arrow than a regular rightwards arrow
canUseHalfwidthRightwardsArrow = false;
}
return new FontInfo({
pixelRatio: PixelRatio.getInstance(targetWindow).value,
fontFamily: bareFontInfo.fontFamily,
fontWeight: bareFontInfo.fontWeight,
fontSize: bareFontInfo.fontSize,
fontFeatureSettings: bareFontInfo.fontFeatureSettings,
fontVariationSettings: bareFontInfo.fontVariationSettings,
lineHeight: bareFontInfo.lineHeight,
letterSpacing: bareFontInfo.letterSpacing,
isMonospace: isMonospace,
typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter.width,
typicalFullwidthCharacterWidth: typicalFullwidthCharacter.width,
canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow,
spaceWidth: space.width,
middotWidth: middot.width,
wsmiddotWidth: wsmiddotWidth.width,
maxDigitWidth: maxDigitWidth
}, true);
}
}
class FontMeasurementsCache {
constructor() {
this._keys = Object.create(null);
this._values = Object.create(null);
}
has(item) {
const itemId = item.getId();
return !!this._values[itemId];
}
get(item) {
const itemId = item.getId();
return this._values[itemId];
}
put(item, value) {
const itemId = item.getId();
this._keys[itemId] = item;
this._values[itemId] = value;
}
remove(item) {
const itemId = item.getId();
delete this._keys[itemId];
delete this._values[itemId];
}
getValues() {
return Object.keys(this._keys).map(id => this._values[id]);
}
}
export const FontMeasurements = new FontMeasurementsImpl();
@@ -0,0 +1,200 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class EditorSettingMigration {
static { this.items = []; }
constructor(key, migrate) {
this.key = key;
this.migrate = migrate;
}
apply(options) {
const value = EditorSettingMigration._read(options, this.key);
const read = (key) => EditorSettingMigration._read(options, key);
const write = (key, value) => EditorSettingMigration._write(options, key, value);
this.migrate(value, read, write);
}
static _read(source, key) {
if (typeof source === 'undefined') {
return undefined;
}
const firstDotIndex = key.indexOf('.');
if (firstDotIndex >= 0) {
const firstSegment = key.substring(0, firstDotIndex);
return this._read(source[firstSegment], key.substring(firstDotIndex + 1));
}
return source[key];
}
static _write(target, key, value) {
const firstDotIndex = key.indexOf('.');
if (firstDotIndex >= 0) {
const firstSegment = key.substring(0, firstDotIndex);
target[firstSegment] = target[firstSegment] || {};
this._write(target[firstSegment], key.substring(firstDotIndex + 1), value);
return;
}
target[key] = value;
}
}
function registerEditorSettingMigration(key, migrate) {
EditorSettingMigration.items.push(new EditorSettingMigration(key, migrate));
}
function registerSimpleEditorSettingMigration(key, values) {
registerEditorSettingMigration(key, (value, read, write) => {
if (typeof value !== 'undefined') {
for (const [oldValue, newValue] of values) {
if (value === oldValue) {
write(key, newValue);
return;
}
}
}
});
}
/**
* Compatibility with old options
*/
export function migrateOptions(options) {
EditorSettingMigration.items.forEach(migration => migration.apply(options));
}
registerSimpleEditorSettingMigration('wordWrap', [[true, 'on'], [false, 'off']]);
registerSimpleEditorSettingMigration('lineNumbers', [[true, 'on'], [false, 'off']]);
registerSimpleEditorSettingMigration('cursorBlinking', [['visible', 'solid']]);
registerSimpleEditorSettingMigration('renderWhitespace', [[true, 'boundary'], [false, 'none']]);
registerSimpleEditorSettingMigration('renderLineHighlight', [[true, 'line'], [false, 'none']]);
registerSimpleEditorSettingMigration('acceptSuggestionOnEnter', [[true, 'on'], [false, 'off']]);
registerSimpleEditorSettingMigration('tabCompletion', [[false, 'off'], [true, 'onlySnippets']]);
registerSimpleEditorSettingMigration('hover', [[true, { enabled: true }], [false, { enabled: false }]]);
registerSimpleEditorSettingMigration('parameterHints', [[true, { enabled: true }], [false, { enabled: false }]]);
registerSimpleEditorSettingMigration('autoIndent', [[false, 'advanced'], [true, 'full']]);
registerSimpleEditorSettingMigration('matchBrackets', [[true, 'always'], [false, 'never']]);
registerSimpleEditorSettingMigration('renderFinalNewline', [[true, 'on'], [false, 'off']]);
registerSimpleEditorSettingMigration('cursorSmoothCaretAnimation', [[true, 'on'], [false, 'off']]);
registerSimpleEditorSettingMigration('occurrencesHighlight', [[true, 'singleFile'], [false, 'off']]);
registerSimpleEditorSettingMigration('wordBasedSuggestions', [[true, 'matchingDocuments'], [false, 'off']]);
registerEditorSettingMigration('autoClosingBrackets', (value, read, write) => {
if (value === false) {
write('autoClosingBrackets', 'never');
if (typeof read('autoClosingQuotes') === 'undefined') {
write('autoClosingQuotes', 'never');
}
if (typeof read('autoSurround') === 'undefined') {
write('autoSurround', 'never');
}
}
});
registerEditorSettingMigration('renderIndentGuides', (value, read, write) => {
if (typeof value !== 'undefined') {
write('renderIndentGuides', undefined);
if (typeof read('guides.indentation') === 'undefined') {
write('guides.indentation', !!value);
}
}
});
registerEditorSettingMigration('highlightActiveIndentGuide', (value, read, write) => {
if (typeof value !== 'undefined') {
write('highlightActiveIndentGuide', undefined);
if (typeof read('guides.highlightActiveIndentation') === 'undefined') {
write('guides.highlightActiveIndentation', !!value);
}
}
});
const suggestFilteredTypesMapping = {
method: 'showMethods',
function: 'showFunctions',
constructor: 'showConstructors',
deprecated: 'showDeprecated',
field: 'showFields',
variable: 'showVariables',
class: 'showClasses',
struct: 'showStructs',
interface: 'showInterfaces',
module: 'showModules',
property: 'showProperties',
event: 'showEvents',
operator: 'showOperators',
unit: 'showUnits',
value: 'showValues',
constant: 'showConstants',
enum: 'showEnums',
enumMember: 'showEnumMembers',
keyword: 'showKeywords',
text: 'showWords',
color: 'showColors',
file: 'showFiles',
reference: 'showReferences',
folder: 'showFolders',
typeParameter: 'showTypeParameters',
snippet: 'showSnippets',
};
registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => {
if (value && typeof value === 'object') {
for (const entry of Object.entries(suggestFilteredTypesMapping)) {
const v = value[entry[0]];
if (v === false) {
if (typeof read(`suggest.${entry[1]}`) === 'undefined') {
write(`suggest.${entry[1]}`, false);
}
}
}
write('suggest.filteredTypes', undefined);
}
});
registerEditorSettingMigration('quickSuggestions', (input, read, write) => {
if (typeof input === 'boolean') {
const value = input ? 'on' : 'off';
const newValue = { comments: value, strings: value, other: value };
write('quickSuggestions', newValue);
}
});
// Sticky Scroll
registerEditorSettingMigration('experimental.stickyScroll.enabled', (value, read, write) => {
if (typeof value === 'boolean') {
write('experimental.stickyScroll.enabled', undefined);
if (typeof read('stickyScroll.enabled') === 'undefined') {
write('stickyScroll.enabled', value);
}
}
});
registerEditorSettingMigration('experimental.stickyScroll.maxLineCount', (value, read, write) => {
if (typeof value === 'number') {
write('experimental.stickyScroll.maxLineCount', undefined);
if (typeof read('stickyScroll.maxLineCount') === 'undefined') {
write('stickyScroll.maxLineCount', value);
}
}
});
// Code Actions on Save
registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => {
if (value && typeof value === 'object') {
let toBeModified = false;
const newValue = {};
for (const entry of Object.entries(value)) {
if (typeof entry[1] === 'boolean') {
toBeModified = true;
newValue[entry[0]] = entry[1] ? 'explicit' : 'never';
}
else {
newValue[entry[0]] = entry[1];
}
}
if (toBeModified) {
write(`codeActionsOnSave`, newValue);
}
}
});
// Migrate Quick Fix Settings
registerEditorSettingMigration('codeActionWidget.includeNearbyQuickfixes', (value, read, write) => {
if (typeof value === 'boolean') {
write('codeActionWidget.includeNearbyQuickfixes', undefined);
if (typeof read('codeActionWidget.includeNearbyQuickFixes') === 'undefined') {
write('codeActionWidget.includeNearbyQuickFixes', value);
}
}
});
// Migrate the lightbulb settings
registerEditorSettingMigration('lightbulb.enabled', (value, read, write) => {
if (typeof value === 'boolean') {
write('lightbulb.enabled', value ? undefined : 'off');
}
});
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from '../../../base/common/event.js';
class TabFocusImpl {
constructor() {
this._tabFocus = false;
this._onDidChangeTabFocus = new Emitter();
this.onDidChangeTabFocus = this._onDidChangeTabFocus.event;
}
getTabFocusMode() {
return this._tabFocus;
}
setTabFocusMode(tabFocusMode) {
this._tabFocus = tabFocusMode;
this._onDidChangeTabFocus.fire(this._tabFocus);
}
}
/**
* Control what pressing Tab does.
* If it is false, pressing Tab or Shift-Tab will be handled by the editor.
* If it is true, pressing Tab or Shift-Tab will move the browser focus.
* Defaults to false.
*/
export const TabFocus = new TabFocusImpl();
@@ -0,0 +1,669 @@
/*---------------------------------------------------------------------------------------------
* 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 { StandardWheelEvent } from '../../../base/browser/mouseEvent.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import * as platform from '../../../base/common/platform.js';
import { HitTestContext, MouseTarget, MouseTargetFactory } from './mouseTarget.js';
import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor, PageCoordinates } from '../editorDom.js';
import { EditorZoom } from '../../common/config/editorZoom.js';
import { Position } from '../../common/core/position.js';
import { Selection } from '../../common/core/selection.js';
import { ViewEventHandler } from '../../common/viewEventHandler.js';
import { MouseWheelClassifier } from '../../../base/browser/ui/scrollbar/scrollableElement.js';
export class MouseHandler extends ViewEventHandler {
constructor(context, viewController, viewHelper) {
super();
this._mouseLeaveMonitor = null;
this._mouseOnOverflowWidgetsDomNode = false;
this._mouseOnViewDomNode = false;
this._context = context;
this.viewController = viewController;
this.viewHelper = viewHelper;
this.mouseTargetFactory = new MouseTargetFactory(this._context, viewHelper);
this._mouseDownOperation = this._register(new MouseDownOperation(this._context, this.viewController, this.viewHelper, this.mouseTargetFactory, (e, testEventTarget) => this._createMouseTargetForView(e, testEventTarget), (e) => this._getMouseColumn(e)));
this.lastMouseLeaveTime = -1;
this._height = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */).height;
const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode);
this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true)));
this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => {
this._mouseOnViewDomNode = true;
this._onMouseMoveOverView(e);
// See https://github.com/microsoft/vscode/issues/138789
// When moving the mouse really quickly, the browser sometimes forgets to
// send us a `mouseleave` or `mouseout` event. We therefore install here
// a global `mousemove` listener to manually recover if the mouse goes outside
// the editor. As soon as the mouse leaves outside of the editor, we
// remove this listener
if (!this._mouseLeaveMonitor) {
this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => {
if (!this.viewHelper.viewDomNode.contains(e.target)) {
// went outside the editor!
this._mouseOnViewDomNode = false;
setTimeout(() => {
if (!this._mouseOnOverflowWidgetsDomNode) {
this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode));
}
}, 0);
}
});
}
}));
this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));
this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => {
this._mouseOnViewDomNode = false;
setTimeout(() => {
if (!this._mouseOnOverflowWidgetsDomNode) {
this._onMouseLeave(e);
}
}, 0);
}));
const overflowWidgetsDomNode = this.viewHelper.overflowWidgetsDomNode;
if (overflowWidgetsDomNode) {
this._register(mouseEvents.onMouseMove(overflowWidgetsDomNode, (e) => {
this._mouseOnOverflowWidgetsDomNode = true;
this._mouseLeaveMonitor?.dispose();
this._mouseLeaveMonitor = null;
this._onMouseMoveOverOverflowWidgetsDomNode(e);
}));
this._register(mouseEvents.onMouseLeave(overflowWidgetsDomNode, (e) => {
this._mouseOnOverflowWidgetsDomNode = false;
setTimeout(() => {
if (!this._mouseOnViewDomNode) {
this._onMouseLeave(e);
}
}, 0);
}));
}
// `pointerdown` events can't be used to determine if there's a double click, or triple click
// because their `e.detail` is always 0.
// We will therefore save the pointer id for the mouse and then reuse it in the `mousedown` event
// for `element.setPointerCapture`.
let capturePointerId = 0;
this._register(mouseEvents.onPointerDown(this.viewHelper.viewDomNode, (e, pointerId) => {
capturePointerId = pointerId;
}));
// The `pointerup` listener registered by `GlobalEditorPointerMoveMonitor` does not get invoked 100% of the times.
// I speculate that this is because the `pointerup` listener is only registered during the `mousedown` event, and perhaps
// the `pointerup` event is already queued for dispatching, which makes it that the new listener doesn't get fired.
// See https://github.com/microsoft/vscode/issues/146486 for repro steps.
// To compensate for that, we simply register here a `pointerup` listener and just communicate it.
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.POINTER_UP, (e) => {
this._mouseDownOperation.onPointerUp();
}));
this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e, capturePointerId)));
this._setupMouseWheelZoomListener();
this._context.addEventHandler(this);
}
_setupMouseWheelZoomListener() {
const classifier = MouseWheelClassifier.INSTANCE;
let prevMouseWheelTime = 0;
let gestureStartZoomLevel = EditorZoom.getZoomLevel();
let gestureHasZoomModifiers = false;
let gestureAccumulatedDelta = 0;
const onMouseWheel = (browserEvent) => {
this.viewController.emitMouseWheel(browserEvent);
if (!this._context.configuration.options.get(76 /* EditorOption.mouseWheelZoom */)) {
return;
}
const e = new StandardWheelEvent(browserEvent);
classifier.acceptStandardWheelEvent(e);
if (classifier.isPhysicalMouseWheel()) {
if (hasMouseWheelZoomModifiers(browserEvent)) {
const zoomLevel = EditorZoom.getZoomLevel();
const delta = e.deltaY > 0 ? 1 : -1;
EditorZoom.setZoomLevel(zoomLevel + delta);
e.preventDefault();
e.stopPropagation();
}
}
else {
// we consider mousewheel events that occur within 50ms of each other to be part of the same gesture
// we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase
// we also want to accumulate deltaY values from the same gesture and use that to set the zoom level
if (Date.now() - prevMouseWheelTime > 50) {
// reset if more than 50ms have passed
gestureStartZoomLevel = EditorZoom.getZoomLevel();
gestureHasZoomModifiers = hasMouseWheelZoomModifiers(browserEvent);
gestureAccumulatedDelta = 0;
}
prevMouseWheelTime = Date.now();
gestureAccumulatedDelta += e.deltaY;
if (gestureHasZoomModifiers) {
EditorZoom.setZoomLevel(gestureStartZoomLevel + gestureAccumulatedDelta / 5);
e.preventDefault();
e.stopPropagation();
}
}
};
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false }));
function hasMouseWheelZoomModifiers(browserEvent) {
return (platform.isMacintosh
// on macOS we support cmd + two fingers scroll (`metaKey` set)
// and also the two fingers pinch gesture (`ctrKey` set)
? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey)
: (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey));
}
}
dispose() {
this._context.removeEventHandler(this);
if (this._mouseLeaveMonitor) {
this._mouseLeaveMonitor.dispose();
this._mouseLeaveMonitor = null;
}
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
// layout change
const height = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */).height;
if (this._height !== height) {
this._height = height;
this._mouseDownOperation.onHeightChanged();
}
}
return false;
}
onCursorStateChanged(e) {
this._mouseDownOperation.onCursorStateChanged(e);
return false;
}
onFocusChanged(e) {
return false;
}
// --- end event handlers
getTargetAtClientPoint(clientX, clientY) {
const clientPos = new ClientCoordinates(clientX, clientY);
const pos = clientPos.toPageCoordinates(dom.getWindow(this.viewHelper.viewDomNode));
const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode);
if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) {
return null;
}
const relativePos = createCoordinatesRelativeToEditor(this.viewHelper.viewDomNode, editorPos, pos);
return this.mouseTargetFactory.createMouseTargetForView(this.viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);
}
_createMouseTargetForView(e, testEventTarget) {
let target = e.target;
if (!this.viewHelper.viewDomNode.contains(target)) {
const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode);
if (shadowRoot) {
target = shadowRoot.elementsFromPoint(e.posx, e.posy).find((el) => this.viewHelper.viewDomNode.contains(el));
}
}
return this.mouseTargetFactory.createMouseTargetForView(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null);
}
_createMouseTargetForOverflowWidgetsDomNode(e) {
return this.mouseTargetFactory.createMouseTargetForOverflowWidgetsDomNode(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, e.target);
}
_getMouseColumn(e) {
return this.mouseTargetFactory.getMouseColumn(e.relativePos);
}
_onContextMenu(e, testEventTarget) {
this.viewController.emitContextMenu({
event: e,
target: this._createMouseTargetForView(e, testEventTarget)
});
}
_onMouseMoveOverView(e) {
this._onMouseMove(e, this._createMouseTargetForView(e, true));
}
_onMouseMoveOverOverflowWidgetsDomNode(e) {
this._onMouseMove(e, this._createMouseTargetForOverflowWidgetsDomNode(e));
}
_onMouseMove(e, target) {
const shouldIgnoreMouseMoveEvent = this._shouldIgnoreMouseMoveEvent(e);
if (shouldIgnoreMouseMoveEvent) {
return undefined;
}
this.viewController.emitMouseMove({
event: e,
target
});
}
_shouldIgnoreMouseMoveEvent(e) {
const targetIsWidget = this.mouseTargetFactory.mouseTargetIsWidget(e);
if (!targetIsWidget) {
e.preventDefault();
}
if (this._mouseDownOperation.isActive()) {
// In selection/drag operation
return true;
}
const actualMouseMoveTime = e.timestamp;
if (actualMouseMoveTime < this.lastMouseLeaveTime) {
// Due to throttling, this event occurred before the mouse left the editor, therefore ignore it.
return true;
}
return false;
}
_onMouseLeave(e) {
if (this._mouseLeaveMonitor) {
this._mouseLeaveMonitor.dispose();
this._mouseLeaveMonitor = null;
}
this.lastMouseLeaveTime = (new Date()).getTime();
this.viewController.emitMouseLeave({
event: e,
target: null
});
}
_onMouseUp(e) {
this.viewController.emitMouseUp({
event: e,
target: this._createMouseTargetForView(e, true)
});
}
_onMouseDown(e, pointerId) {
const t = this._createMouseTargetForView(e, true);
const targetIsContent = (t.type === 6 /* MouseTargetType.CONTENT_TEXT */ || t.type === 7 /* MouseTargetType.CONTENT_EMPTY */);
const targetIsGutter = (t.type === 2 /* MouseTargetType.GUTTER_GLYPH_MARGIN */ || t.type === 3 /* MouseTargetType.GUTTER_LINE_NUMBERS */ || t.type === 4 /* MouseTargetType.GUTTER_LINE_DECORATIONS */);
const targetIsLineNumbers = (t.type === 3 /* MouseTargetType.GUTTER_LINE_NUMBERS */);
const selectOnLineNumbers = this._context.configuration.options.get(110 /* EditorOption.selectOnLineNumbers */);
const targetIsViewZone = (t.type === 8 /* MouseTargetType.CONTENT_VIEW_ZONE */ || t.type === 5 /* MouseTargetType.GUTTER_VIEW_ZONE */);
const targetIsWidget = (t.type === 9 /* MouseTargetType.CONTENT_WIDGET */);
let shouldHandle = e.leftButton || e.middleButton;
if (platform.isMacintosh && e.leftButton && e.ctrlKey) {
shouldHandle = false;
}
const focus = () => {
e.preventDefault();
this.viewHelper.focusTextArea();
};
if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) {
focus();
this._mouseDownOperation.start(t.type, e, pointerId);
}
else if (targetIsGutter) {
// Do not steal focus
e.preventDefault();
}
else if (targetIsViewZone) {
const viewZoneData = t.detail;
if (shouldHandle && this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) {
focus();
this._mouseDownOperation.start(t.type, e, pointerId);
e.preventDefault();
}
}
else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(t.detail)) {
focus();
e.preventDefault();
}
this.viewController.emitMouseDown({
event: e,
target: t
});
}
}
class MouseDownOperation extends Disposable {
constructor(_context, _viewController, _viewHelper, _mouseTargetFactory, createMouseTarget, getMouseColumn) {
super();
this._context = _context;
this._viewController = _viewController;
this._viewHelper = _viewHelper;
this._mouseTargetFactory = _mouseTargetFactory;
this._createMouseTarget = createMouseTarget;
this._getMouseColumn = getMouseColumn;
this._mouseMoveMonitor = this._register(new GlobalEditorPointerMoveMonitor(this._viewHelper.viewDomNode));
this._topBottomDragScrolling = this._register(new TopBottomDragScrolling(this._context, this._viewHelper, this._mouseTargetFactory, (position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)));
this._mouseState = new MouseDownState();
this._currentSelection = new Selection(1, 1, 1, 1);
this._isActive = false;
this._lastMouseEvent = null;
}
dispose() {
super.dispose();
}
isActive() {
return this._isActive;
}
_onMouseDownThenMove(e) {
this._lastMouseEvent = e;
this._mouseState.setModifiers(e);
const position = this._findMousePosition(e, false);
if (!position) {
// Ignoring because position is unknown
return;
}
if (this._mouseState.isDragAndDrop) {
this._viewController.emitMouseDrag({
event: e,
target: position
});
}
else {
if (position.type === 13 /* MouseTargetType.OUTSIDE_EDITOR */ && (position.outsidePosition === 'above' || position.outsidePosition === 'below')) {
this._topBottomDragScrolling.start(position, e);
}
else {
this._topBottomDragScrolling.stop();
this._dispatchMouse(position, true, 1 /* NavigationCommandRevealType.Minimal */);
}
}
}
start(targetType, e, pointerId) {
this._lastMouseEvent = e;
this._mouseState.setStartedOnLineNumbers(targetType === 3 /* MouseTargetType.GUTTER_LINE_NUMBERS */);
this._mouseState.setStartButtons(e);
this._mouseState.setModifiers(e);
const position = this._findMousePosition(e, true);
if (!position || !position.position) {
// Ignoring because position is unknown
return;
}
this._mouseState.trySetCount(e.detail, position.position);
// Overwrite the detail of the MouseEvent, as it will be sent out in an event and contributions might rely on it.
e.detail = this._mouseState.count;
const options = this._context.configuration.options;
if (!options.get(92 /* EditorOption.readOnly */)
&& options.get(35 /* EditorOption.dragAndDrop */)
&& !options.get(22 /* EditorOption.columnSelection */)
&& !this._mouseState.altKey // we don't support multiple mouse
&& e.detail < 2 // only single click on a selection can work
&& !this._isActive // the mouse is not down yet
&& !this._currentSelection.isEmpty() // we don't drag single cursor
&& (position.type === 6 /* MouseTargetType.CONTENT_TEXT */) // single click on text
&& position.position && this._currentSelection.containsPosition(position.position) // single click on a selection
) {
this._mouseState.isDragAndDrop = true;
this._isActive = true;
this._mouseMoveMonitor.startMonitoring(this._viewHelper.viewLinesDomNode, pointerId, e.buttons, (e) => this._onMouseDownThenMove(e), (browserEvent) => {
const position = this._findMousePosition(this._lastMouseEvent, false);
if (dom.isKeyboardEvent(browserEvent)) {
// cancel
this._viewController.emitMouseDropCanceled();
}
else {
this._viewController.emitMouseDrop({
event: this._lastMouseEvent,
target: (position ? this._createMouseTarget(this._lastMouseEvent, true) : null) // Ignoring because position is unknown, e.g., Content View Zone
});
}
this._stop();
});
return;
}
this._mouseState.isDragAndDrop = false;
this._dispatchMouse(position, e.shiftKey, 1 /* NavigationCommandRevealType.Minimal */);
if (!this._isActive) {
this._isActive = true;
this._mouseMoveMonitor.startMonitoring(this._viewHelper.viewLinesDomNode, pointerId, e.buttons, (e) => this._onMouseDownThenMove(e), () => this._stop());
}
}
_stop() {
this._isActive = false;
this._topBottomDragScrolling.stop();
}
onHeightChanged() {
this._mouseMoveMonitor.stopMonitoring();
}
onPointerUp() {
this._mouseMoveMonitor.stopMonitoring();
}
onCursorStateChanged(e) {
this._currentSelection = e.selections[0];
}
_getPositionOutsideEditor(e) {
const editorContent = e.editorPos;
const model = this._context.viewModel;
const viewLayout = this._context.viewLayout;
const mouseColumn = this._getMouseColumn(e);
if (e.posy < editorContent.y) {
const outsideDistance = editorContent.y - e.posy;
const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - outsideDistance, 0);
const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
if (viewZoneData) {
const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
if (newPosition) {
return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'above', outsideDistance);
}
}
const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1), 'above', outsideDistance);
}
if (e.posy > editorContent.y + editorContent.height) {
const outsideDistance = e.posy - editorContent.y - editorContent.height;
const verticalOffset = viewLayout.getCurrentScrollTop() + e.relativePos.y;
const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);
if (viewZoneData) {
const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);
if (newPosition) {
return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'below', outsideDistance);
}
}
const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);
return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)), 'below', outsideDistance);
}
const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + e.relativePos.y);
if (e.posx < editorContent.x) {
const outsideDistance = editorContent.x - e.posx;
return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1), 'left', outsideDistance);
}
if (e.posx > editorContent.x + editorContent.width) {
const outsideDistance = e.posx - editorContent.x - editorContent.width;
return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance);
}
return null;
}
_findMousePosition(e, testEventTarget) {
const positionOutsideEditor = this._getPositionOutsideEditor(e);
if (positionOutsideEditor) {
return positionOutsideEditor;
}
const t = this._createMouseTarget(e, testEventTarget);
const hintedPosition = t.position;
if (!hintedPosition) {
return null;
}
if (t.type === 8 /* MouseTargetType.CONTENT_VIEW_ZONE */ || t.type === 5 /* MouseTargetType.GUTTER_VIEW_ZONE */) {
const newPosition = this._helpPositionJumpOverViewZone(t.detail);
if (newPosition) {
return MouseTarget.createViewZone(t.type, t.element, t.mouseColumn, newPosition, t.detail);
}
}
return t;
}
_helpPositionJumpOverViewZone(viewZoneData) {
// Force position on view zones to go above or below depending on where selection started from
const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);
const positionBefore = viewZoneData.positionBefore;
const positionAfter = viewZoneData.positionAfter;
if (positionBefore && positionAfter) {
if (positionBefore.isBefore(selectionStart)) {
return positionBefore;
}
else {
return positionAfter;
}
}
return null;
}
_dispatchMouse(position, inSelectionMode, revealType) {
if (!position.position) {
return;
}
this._viewController.dispatchMouse({
position: position.position,
mouseColumn: position.mouseColumn,
startedOnLineNumbers: this._mouseState.startedOnLineNumbers,
revealType,
inSelectionMode: inSelectionMode,
mouseDownCount: this._mouseState.count,
altKey: this._mouseState.altKey,
ctrlKey: this._mouseState.ctrlKey,
metaKey: this._mouseState.metaKey,
shiftKey: this._mouseState.shiftKey,
leftButton: this._mouseState.leftButton,
middleButton: this._mouseState.middleButton,
onInjectedText: position.type === 6 /* MouseTargetType.CONTENT_TEXT */ && position.detail.injectedText !== null
});
}
}
class TopBottomDragScrolling extends Disposable {
constructor(_context, _viewHelper, _mouseTargetFactory, _dispatchMouse) {
super();
this._context = _context;
this._viewHelper = _viewHelper;
this._mouseTargetFactory = _mouseTargetFactory;
this._dispatchMouse = _dispatchMouse;
this._operation = null;
}
dispose() {
super.dispose();
this.stop();
}
start(position, mouseEvent) {
if (this._operation) {
this._operation.setPosition(position, mouseEvent);
}
else {
this._operation = new TopBottomDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent);
}
}
stop() {
if (this._operation) {
this._operation.dispose();
this._operation = null;
}
}
}
class TopBottomDragScrollingOperation extends Disposable {
constructor(_context, _viewHelper, _mouseTargetFactory, _dispatchMouse, position, mouseEvent) {
super();
this._context = _context;
this._viewHelper = _viewHelper;
this._mouseTargetFactory = _mouseTargetFactory;
this._dispatchMouse = _dispatchMouse;
this._position = position;
this._mouseEvent = mouseEvent;
this._lastTime = Date.now();
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseEvent.browserEvent), () => this._execute());
}
dispose() {
this._animationFrameDisposable.dispose();
super.dispose();
}
setPosition(position, mouseEvent) {
this._position = position;
this._mouseEvent = mouseEvent;
}
/**
* update internal state and return elapsed ms since last time
*/
_tick() {
const now = Date.now();
const elapsed = now - this._lastTime;
this._lastTime = now;
return elapsed;
}
/**
* get the number of lines per second to auto-scroll
*/
_getScrollSpeed() {
const lineHeight = this._context.configuration.options.get(67 /* EditorOption.lineHeight */);
const viewportInLines = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */).height / lineHeight;
const outsideDistanceInLines = this._position.outsideDistance / lineHeight;
if (outsideDistanceInLines <= 1.5) {
return Math.max(30, viewportInLines * (1 + outsideDistanceInLines));
}
if (outsideDistanceInLines <= 3) {
return Math.max(60, viewportInLines * (2 + outsideDistanceInLines));
}
return Math.max(200, viewportInLines * (7 + outsideDistanceInLines));
}
_execute() {
const lineHeight = this._context.configuration.options.get(67 /* EditorOption.lineHeight */);
const scrollSpeedInLines = this._getScrollSpeed();
const elapsed = this._tick();
const scrollInPixels = scrollSpeedInLines * (elapsed / 1000) * lineHeight;
const scrollValue = (this._position.outsidePosition === 'above' ? -scrollInPixels : scrollInPixels);
this._context.viewModel.viewLayout.deltaScrollNow(0, scrollValue);
this._viewHelper.renderNow();
const viewportData = this._context.viewLayout.getLinesViewportData();
const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber);
// First, try to find a position that matches the horizontal position of the mouse
let mouseTarget;
{
const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode);
const horizontalScrollbarHeight = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */).horizontalScrollbarHeight;
const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1);
const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos);
mouseTarget = this._mouseTargetFactory.createMouseTargetForView(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);
}
if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) {
if (this._position.outsidePosition === 'above') {
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance);
}
else {
mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance);
}
}
this._dispatchMouse(mouseTarget, true, 2 /* NavigationCommandRevealType.None */);
this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute());
}
}
class MouseDownState {
static { this.CLEAR_MOUSE_DOWN_COUNT_TIME = 400; } // ms
get altKey() { return this._altKey; }
get ctrlKey() { return this._ctrlKey; }
get metaKey() { return this._metaKey; }
get shiftKey() { return this._shiftKey; }
get leftButton() { return this._leftButton; }
get middleButton() { return this._middleButton; }
get startedOnLineNumbers() { return this._startedOnLineNumbers; }
constructor() {
this._altKey = false;
this._ctrlKey = false;
this._metaKey = false;
this._shiftKey = false;
this._leftButton = false;
this._middleButton = false;
this._startedOnLineNumbers = false;
this._lastMouseDownPosition = null;
this._lastMouseDownPositionEqualCount = 0;
this._lastMouseDownCount = 0;
this._lastSetMouseDownCountTime = 0;
this.isDragAndDrop = false;
}
get count() {
return this._lastMouseDownCount;
}
setModifiers(source) {
this._altKey = source.altKey;
this._ctrlKey = source.ctrlKey;
this._metaKey = source.metaKey;
this._shiftKey = source.shiftKey;
}
setStartButtons(source) {
this._leftButton = source.leftButton;
this._middleButton = source.middleButton;
}
setStartedOnLineNumbers(startedOnLineNumbers) {
this._startedOnLineNumbers = startedOnLineNumbers;
}
trySetCount(setMouseDownCount, newMouseDownPosition) {
// a. Invalidate multiple clicking if too much time has passed (will be hit by IE because the detail field of mouse events contains garbage in IE10)
const currentTime = (new Date()).getTime();
if (currentTime - this._lastSetMouseDownCountTime > MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME) {
setMouseDownCount = 1;
}
this._lastSetMouseDownCountTime = currentTime;
// b. Ensure that we don't jump from single click to triple click in one go (will be hit by IE because the detail field of mouse events contains garbage in IE10)
if (setMouseDownCount > this._lastMouseDownCount + 1) {
setMouseDownCount = this._lastMouseDownCount + 1;
}
// c. Invalidate multiple clicking if the logical position is different
if (this._lastMouseDownPosition && this._lastMouseDownPosition.equals(newMouseDownPosition)) {
this._lastMouseDownPositionEqualCount++;
}
else {
this._lastMouseDownPositionEqualCount = 1;
}
this._lastMouseDownPosition = newMouseDownPosition;
// Finally set the lastMouseDownCount
this._lastMouseDownCount = Math.min(setMouseDownCount, this._lastMouseDownPositionEqualCount);
}
}
@@ -0,0 +1,935 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PageCoordinates } from '../editorDom.js';
import { PartFingerprints } from '../view/viewPart.js';
import { ViewLine } from '../viewParts/lines/viewLine.js';
import { Position } from '../../common/core/position.js';
import { Range as EditorRange } from '../../common/core/range.js';
import { CursorColumns } from '../../common/core/cursorColumns.js';
import * as dom from '../../../base/browser/dom.js';
import { AtomicTabMoveOperations } from '../../common/cursor/cursorAtomicMoveOperations.js';
import { Lazy } from '../../../base/common/lazy.js';
class UnknownHitTestResult {
constructor(hitTarget = null) {
this.hitTarget = hitTarget;
this.type = 0 /* HitTestResultType.Unknown */;
}
}
class ContentHitTestResult {
get hitTarget() { return this.spanNode; }
constructor(position, spanNode, injectedText) {
this.position = position;
this.spanNode = spanNode;
this.injectedText = injectedText;
this.type = 1 /* HitTestResultType.Content */;
}
}
var HitTestResult;
(function (HitTestResult) {
function createFromDOMInfo(ctx, spanNode, offset) {
const position = ctx.getPositionFromDOMInfo(spanNode, offset);
if (position) {
return new ContentHitTestResult(position, spanNode, null);
}
return new UnknownHitTestResult(spanNode);
}
HitTestResult.createFromDOMInfo = createFromDOMInfo;
})(HitTestResult || (HitTestResult = {}));
export class PointerHandlerLastRenderData {
constructor(lastViewCursorsRenderData, lastTextareaPosition) {
this.lastViewCursorsRenderData = lastViewCursorsRenderData;
this.lastTextareaPosition = lastTextareaPosition;
}
}
export class MouseTarget {
static _deduceRage(position, range = null) {
if (!range && position) {
return new EditorRange(position.lineNumber, position.column, position.lineNumber, position.column);
}
return range ?? null;
}
static createUnknown(element, mouseColumn, position) {
return { type: 0 /* MouseTargetType.UNKNOWN */, element, mouseColumn, position, range: this._deduceRage(position) };
}
static createTextarea(element, mouseColumn) {
return { type: 1 /* MouseTargetType.TEXTAREA */, element, mouseColumn, position: null, range: null };
}
static createMargin(type, element, mouseColumn, position, range, detail) {
return { type, element, mouseColumn, position, range, detail };
}
static createViewZone(type, element, mouseColumn, position, detail) {
return { type, element, mouseColumn, position, range: this._deduceRage(position), detail };
}
static createContentText(element, mouseColumn, position, range, detail) {
return { type: 6 /* MouseTargetType.CONTENT_TEXT */, element, mouseColumn, position, range: this._deduceRage(position, range), detail };
}
static createContentEmpty(element, mouseColumn, position, detail) {
return { type: 7 /* MouseTargetType.CONTENT_EMPTY */, element, mouseColumn, position, range: this._deduceRage(position), detail };
}
static createContentWidget(element, mouseColumn, detail) {
return { type: 9 /* MouseTargetType.CONTENT_WIDGET */, element, mouseColumn, position: null, range: null, detail };
}
static createScrollbar(element, mouseColumn, position) {
return { type: 11 /* MouseTargetType.SCROLLBAR */, element, mouseColumn, position, range: this._deduceRage(position) };
}
static createOverlayWidget(element, mouseColumn, detail) {
return { type: 12 /* MouseTargetType.OVERLAY_WIDGET */, element, mouseColumn, position: null, range: null, detail };
}
static createOutsideEditor(mouseColumn, position, outsidePosition, outsideDistance) {
return { type: 13 /* MouseTargetType.OUTSIDE_EDITOR */, element: null, mouseColumn, position, range: this._deduceRage(position), outsidePosition, outsideDistance };
}
static _typeToString(type) {
if (type === 1 /* MouseTargetType.TEXTAREA */) {
return 'TEXTAREA';
}
if (type === 2 /* MouseTargetType.GUTTER_GLYPH_MARGIN */) {
return 'GUTTER_GLYPH_MARGIN';
}
if (type === 3 /* MouseTargetType.GUTTER_LINE_NUMBERS */) {
return 'GUTTER_LINE_NUMBERS';
}
if (type === 4 /* MouseTargetType.GUTTER_LINE_DECORATIONS */) {
return 'GUTTER_LINE_DECORATIONS';
}
if (type === 5 /* MouseTargetType.GUTTER_VIEW_ZONE */) {
return 'GUTTER_VIEW_ZONE';
}
if (type === 6 /* MouseTargetType.CONTENT_TEXT */) {
return 'CONTENT_TEXT';
}
if (type === 7 /* MouseTargetType.CONTENT_EMPTY */) {
return 'CONTENT_EMPTY';
}
if (type === 8 /* MouseTargetType.CONTENT_VIEW_ZONE */) {
return 'CONTENT_VIEW_ZONE';
}
if (type === 9 /* MouseTargetType.CONTENT_WIDGET */) {
return 'CONTENT_WIDGET';
}
if (type === 10 /* MouseTargetType.OVERVIEW_RULER */) {
return 'OVERVIEW_RULER';
}
if (type === 11 /* MouseTargetType.SCROLLBAR */) {
return 'SCROLLBAR';
}
if (type === 12 /* MouseTargetType.OVERLAY_WIDGET */) {
return 'OVERLAY_WIDGET';
}
return 'UNKNOWN';
}
static toString(target) {
return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify(target.detail);
}
}
class ElementPath {
static isTextArea(path) {
return (path.length === 2
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[1] === 7 /* PartFingerprint.TextArea */);
}
static isChildOfViewLines(path) {
return (path.length >= 4
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[3] === 8 /* PartFingerprint.ViewLines */);
}
static isStrictChildOfViewLines(path) {
return (path.length > 4
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[3] === 8 /* PartFingerprint.ViewLines */);
}
static isChildOfScrollableElement(path) {
return (path.length >= 2
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[1] === 6 /* PartFingerprint.ScrollableElement */);
}
static isChildOfMinimap(path) {
return (path.length >= 2
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[1] === 9 /* PartFingerprint.Minimap */);
}
static isChildOfContentWidgets(path) {
return (path.length >= 4
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[3] === 1 /* PartFingerprint.ContentWidgets */);
}
static isChildOfOverflowGuard(path) {
return (path.length >= 1
&& path[0] === 3 /* PartFingerprint.OverflowGuard */);
}
static isChildOfOverflowingContentWidgets(path) {
return (path.length >= 1
&& path[0] === 2 /* PartFingerprint.OverflowingContentWidgets */);
}
static isChildOfOverlayWidgets(path) {
return (path.length >= 2
&& path[0] === 3 /* PartFingerprint.OverflowGuard */
&& path[1] === 4 /* PartFingerprint.OverlayWidgets */);
}
static isChildOfOverflowingOverlayWidgets(path) {
return (path.length >= 1
&& path[0] === 5 /* PartFingerprint.OverflowingOverlayWidgets */);
}
}
export class HitTestContext {
constructor(context, viewHelper, lastRenderData) {
this.viewModel = context.viewModel;
const options = context.configuration.options;
this.layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this.viewDomNode = viewHelper.viewDomNode;
this.overflowWidgetsDomNode = viewHelper.overflowWidgetsDomNode ?? null;
this.lineHeight = options.get(67 /* EditorOption.lineHeight */);
this.stickyTabStops = options.get(117 /* EditorOption.stickyTabStops */);
this.typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
this.lastRenderData = lastRenderData;
this._context = context;
this._viewHelper = viewHelper;
}
getZoneAtCoord(mouseVerticalOffset) {
return HitTestContext.getZoneAtCoord(this._context, mouseVerticalOffset);
}
static getZoneAtCoord(context, mouseVerticalOffset) {
// The target is either a view zone or the empty space after the last view-line
const viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset);
if (viewZoneWhitespace) {
const viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2;
const lineCount = context.viewModel.getLineCount();
let positionBefore = null;
let position;
let positionAfter = null;
if (viewZoneWhitespace.afterLineNumber !== lineCount) {
// There are more lines after this view zone
positionAfter = new Position(viewZoneWhitespace.afterLineNumber + 1, 1);
}
if (viewZoneWhitespace.afterLineNumber > 0) {
// There are more lines above this view zone
positionBefore = new Position(viewZoneWhitespace.afterLineNumber, context.viewModel.getLineMaxColumn(viewZoneWhitespace.afterLineNumber));
}
if (positionAfter === null) {
position = positionBefore;
}
else if (positionBefore === null) {
position = positionAfter;
}
else if (mouseVerticalOffset < viewZoneMiddle) {
position = positionBefore;
}
else {
position = positionAfter;
}
return {
viewZoneId: viewZoneWhitespace.id,
afterLineNumber: viewZoneWhitespace.afterLineNumber,
positionBefore: positionBefore,
positionAfter: positionAfter,
position: position
};
}
return null;
}
getFullLineRangeAtCoord(mouseVerticalOffset) {
if (this._context.viewLayout.isAfterLines(mouseVerticalOffset)) {
// Below the last line
const lineNumber = this._context.viewModel.getLineCount();
const maxLineColumn = this._context.viewModel.getLineMaxColumn(lineNumber);
return {
range: new EditorRange(lineNumber, maxLineColumn, lineNumber, maxLineColumn),
isAfterLines: true
};
}
const lineNumber = this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset);
const maxLineColumn = this._context.viewModel.getLineMaxColumn(lineNumber);
return {
range: new EditorRange(lineNumber, 1, lineNumber, maxLineColumn),
isAfterLines: false
};
}
getLineNumberAtVerticalOffset(mouseVerticalOffset) {
return this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset);
}
isAfterLines(mouseVerticalOffset) {
return this._context.viewLayout.isAfterLines(mouseVerticalOffset);
}
isInTopPadding(mouseVerticalOffset) {
return this._context.viewLayout.isInTopPadding(mouseVerticalOffset);
}
isInBottomPadding(mouseVerticalOffset) {
return this._context.viewLayout.isInBottomPadding(mouseVerticalOffset);
}
getVerticalOffsetForLineNumber(lineNumber) {
return this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber);
}
findAttribute(element, attr) {
return HitTestContext._findAttribute(element, attr, this._viewHelper.viewDomNode);
}
static _findAttribute(element, attr, stopAt) {
while (element && element !== element.ownerDocument.body) {
if (element.hasAttribute && element.hasAttribute(attr)) {
return element.getAttribute(attr);
}
if (element === stopAt) {
return null;
}
element = element.parentNode;
}
return null;
}
getLineWidth(lineNumber) {
return this._viewHelper.getLineWidth(lineNumber);
}
visibleRangeForPosition(lineNumber, column) {
return this._viewHelper.visibleRangeForPosition(lineNumber, column);
}
getPositionFromDOMInfo(spanNode, offset) {
return this._viewHelper.getPositionFromDOMInfo(spanNode, offset);
}
getCurrentScrollTop() {
return this._context.viewLayout.getCurrentScrollTop();
}
getCurrentScrollLeft() {
return this._context.viewLayout.getCurrentScrollLeft();
}
}
class BareHitTestRequest {
constructor(ctx, editorPos, pos, relativePos) {
this.editorPos = editorPos;
this.pos = pos;
this.relativePos = relativePos;
this.mouseVerticalOffset = Math.max(0, ctx.getCurrentScrollTop() + this.relativePos.y);
this.mouseContentHorizontalOffset = ctx.getCurrentScrollLeft() + this.relativePos.x - ctx.layoutInfo.contentLeft;
this.isInMarginArea = (this.relativePos.x < ctx.layoutInfo.contentLeft && this.relativePos.x >= ctx.layoutInfo.glyphMarginLeft);
this.isInContentArea = !this.isInMarginArea;
this.mouseColumn = Math.max(0, MouseTargetFactory._getMouseColumn(this.mouseContentHorizontalOffset, ctx.typicalHalfwidthCharacterWidth));
}
}
class HitTestRequest extends BareHitTestRequest {
get target() {
if (this._useHitTestTarget) {
return this.hitTestResult.value.hitTarget;
}
return this._eventTarget;
}
get targetPath() {
if (this._targetPathCacheElement !== this.target && this._targetElement) {
this._targetPathCacheElement = this.target;
this._targetPathCacheValue = PartFingerprints.collect(this.target, this._targetElement);
}
return this._targetPathCacheValue;
}
constructor(ctx, editorPos, pos, relativePos, eventTarget, targetElement = null) {
super(ctx, editorPos, pos, relativePos);
this.hitTestResult = new Lazy(() => MouseTargetFactory.doHitTest(this._ctx, this));
this._targetPathCacheElement = null;
this._targetPathCacheValue = new Uint8Array(0);
this._targetElement = null;
this._ctx = ctx;
this._eventTarget = eventTarget;
this._targetElement = targetElement;
// If no event target is passed in, we will use the hit test target
const hasEventTarget = Boolean(this._eventTarget);
this._useHitTestTarget = !hasEventTarget;
}
toString() {
return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), relativePos(${this.relativePos.x},${this.relativePos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? this.target.outerHTML : null}`;
}
get wouldBenefitFromHitTestTargetSwitch() {
return (!this._useHitTestTarget
&& this.hitTestResult.value.hitTarget !== null
&& this.target !== this.hitTestResult.value.hitTarget);
}
switchToHitTestTarget() {
this._useHitTestTarget = true;
}
_getMouseColumn(position = null) {
if (position && position.column < this._ctx.viewModel.getLineMaxColumn(position.lineNumber)) {
// Most likely, the line contains foreign decorations...
return CursorColumns.visibleColumnFromColumn(this._ctx.viewModel.getLineContent(position.lineNumber), position.column, this._ctx.viewModel.model.getOptions().tabSize) + 1;
}
return this.mouseColumn;
}
fulfillUnknown(position = null) {
return MouseTarget.createUnknown(this.target, this._getMouseColumn(position), position);
}
fulfillTextarea() {
return MouseTarget.createTextarea(this.target, this._getMouseColumn());
}
fulfillMargin(type, position, range, detail) {
return MouseTarget.createMargin(type, this.target, this._getMouseColumn(position), position, range, detail);
}
fulfillViewZone(type, position, detail) {
return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(position), position, detail);
}
fulfillContentText(position, range, detail) {
return MouseTarget.createContentText(this.target, this._getMouseColumn(position), position, range, detail);
}
fulfillContentEmpty(position, detail) {
return MouseTarget.createContentEmpty(this.target, this._getMouseColumn(position), position, detail);
}
fulfillContentWidget(detail) {
return MouseTarget.createContentWidget(this.target, this._getMouseColumn(), detail);
}
fulfillScrollbar(position) {
return MouseTarget.createScrollbar(this.target, this._getMouseColumn(position), position);
}
fulfillOverlayWidget(detail) {
return MouseTarget.createOverlayWidget(this.target, this._getMouseColumn(), detail);
}
}
const EMPTY_CONTENT_AFTER_LINES = { isAfterLines: true };
function createEmptyContentDataInLines(horizontalDistanceToText) {
return {
isAfterLines: false,
horizontalDistanceToText: horizontalDistanceToText
};
}
export class MouseTargetFactory {
constructor(context, viewHelper) {
this._context = context;
this._viewHelper = viewHelper;
}
mouseTargetIsWidget(e) {
const t = e.target;
const path = PartFingerprints.collect(t, this._viewHelper.viewDomNode);
// Is it a content widget?
if (ElementPath.isChildOfContentWidgets(path) || ElementPath.isChildOfOverflowingContentWidgets(path)) {
return true;
}
// Is it an overlay widget?
if (ElementPath.isChildOfOverlayWidgets(path) || ElementPath.isChildOfOverflowingOverlayWidgets(path)) {
return true;
}
return false;
}
createMouseTargetForView(lastRenderData, editorPos, pos, relativePos, target) {
const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData);
const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target, ctx.viewDomNode);
try {
const r = MouseTargetFactory._createMouseTarget(ctx, request);
if (r.type === 6 /* MouseTargetType.CONTENT_TEXT */) {
// Snap to the nearest soft tab boundary if atomic soft tabs are enabled.
if (ctx.stickyTabStops && r.position !== null) {
const position = MouseTargetFactory._snapToSoftTabBoundary(r.position, ctx.viewModel);
const range = EditorRange.fromPositions(position, position).plusRange(r.range);
return request.fulfillContentText(position, range, r.detail);
}
}
// console.log(MouseTarget.toString(r));
return r;
}
catch (err) {
// console.log(err);
return request.fulfillUnknown();
}
}
createMouseTargetForOverflowWidgetsDomNode(lastRenderData, editorPos, pos, relativePos, target) {
const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData);
const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target, ctx.overflowWidgetsDomNode);
try {
return MouseTargetFactory._createMouseTarget(ctx, request);
}
catch (err) {
return request.fulfillUnknown();
}
}
static _createMouseTarget(ctx, request) {
// console.log(`${domHitTestExecuted ? '=>' : ''}CAME IN REQUEST: ${request}`);
if (request.target === null) {
// No target
return request.fulfillUnknown();
}
// we know for a fact that request.target is not null
const resolvedRequest = request;
let result = null;
if (!ElementPath.isChildOfOverflowGuard(request.targetPath) && !ElementPath.isChildOfOverflowingContentWidgets(request.targetPath) && !ElementPath.isChildOfOverflowingOverlayWidgets(request.targetPath)) {
// We only render dom nodes inside the overflow guard or in the overflowing content widgets
result = result || request.fulfillUnknown();
}
result = result || MouseTargetFactory._hitTestContentWidget(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestOverlayWidget(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestMinimap(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestScrollbarSlider(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestViewZone(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestMargin(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestViewCursor(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestTextArea(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestViewLines(ctx, resolvedRequest);
result = result || MouseTargetFactory._hitTestScrollbar(ctx, resolvedRequest);
return (result || request.fulfillUnknown());
}
static _hitTestContentWidget(ctx, request) {
// Is it a content widget?
if (ElementPath.isChildOfContentWidgets(request.targetPath) || ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) {
const widgetId = ctx.findAttribute(request.target, 'widgetId');
if (widgetId) {
return request.fulfillContentWidget(widgetId);
}
else {
return request.fulfillUnknown();
}
}
return null;
}
static _hitTestOverlayWidget(ctx, request) {
// Is it an overlay widget?
if (ElementPath.isChildOfOverlayWidgets(request.targetPath) || ElementPath.isChildOfOverflowingOverlayWidgets(request.targetPath)) {
const widgetId = ctx.findAttribute(request.target, 'widgetId');
if (widgetId) {
return request.fulfillOverlayWidget(widgetId);
}
else {
return request.fulfillUnknown();
}
}
return null;
}
static _hitTestViewCursor(ctx, request) {
if (request.target) {
// Check if we've hit a painted cursor
const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData;
for (const d of lastViewCursorsRenderData) {
if (request.target === d.domNode) {
return request.fulfillContentText(d.position, null, { mightBeForeignElement: false, injectedText: null });
}
}
}
if (request.isInContentArea) {
// Edge has a bug when hit-testing the exact position of a cursor,
// instead of returning the correct dom node, it returns the
// first or last rendered view line dom node, therefore help it out
// and first check if we are on top of a cursor
const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData;
const mouseContentHorizontalOffset = request.mouseContentHorizontalOffset;
const mouseVerticalOffset = request.mouseVerticalOffset;
for (const d of lastViewCursorsRenderData) {
if (mouseContentHorizontalOffset < d.contentLeft) {
// mouse position is to the left of the cursor
continue;
}
if (mouseContentHorizontalOffset > d.contentLeft + d.width) {
// mouse position is to the right of the cursor
continue;
}
const cursorVerticalOffset = ctx.getVerticalOffsetForLineNumber(d.position.lineNumber);
if (cursorVerticalOffset <= mouseVerticalOffset
&& mouseVerticalOffset <= cursorVerticalOffset + d.height) {
return request.fulfillContentText(d.position, null, { mightBeForeignElement: false, injectedText: null });
}
}
}
return null;
}
static _hitTestViewZone(ctx, request) {
const viewZoneData = ctx.getZoneAtCoord(request.mouseVerticalOffset);
if (viewZoneData) {
const mouseTargetType = (request.isInContentArea ? 8 /* MouseTargetType.CONTENT_VIEW_ZONE */ : 5 /* MouseTargetType.GUTTER_VIEW_ZONE */);
return request.fulfillViewZone(mouseTargetType, viewZoneData.position, viewZoneData);
}
return null;
}
static _hitTestTextArea(ctx, request) {
// Is it the textarea?
if (ElementPath.isTextArea(request.targetPath)) {
if (ctx.lastRenderData.lastTextareaPosition) {
return request.fulfillContentText(ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false, injectedText: null });
}
return request.fulfillTextarea();
}
return null;
}
static _hitTestMargin(ctx, request) {
if (request.isInMarginArea) {
const res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset);
const pos = res.range.getStartPosition();
let offset = Math.abs(request.relativePos.x);
const detail = {
isAfterLines: res.isAfterLines,
glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft,
glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth,
lineNumbersWidth: ctx.layoutInfo.lineNumbersWidth,
offsetX: offset
};
offset -= ctx.layoutInfo.glyphMarginLeft;
if (offset <= ctx.layoutInfo.glyphMarginWidth) {
// On the glyph margin
const modelCoordinate = ctx.viewModel.coordinatesConverter.convertViewPositionToModelPosition(res.range.getStartPosition());
const lanes = ctx.viewModel.glyphLanes.getLanesAtLine(modelCoordinate.lineNumber);
detail.glyphMarginLane = lanes[Math.floor(offset / ctx.lineHeight)];
return request.fulfillMargin(2 /* MouseTargetType.GUTTER_GLYPH_MARGIN */, pos, res.range, detail);
}
offset -= ctx.layoutInfo.glyphMarginWidth;
if (offset <= ctx.layoutInfo.lineNumbersWidth) {
// On the line numbers
return request.fulfillMargin(3 /* MouseTargetType.GUTTER_LINE_NUMBERS */, pos, res.range, detail);
}
offset -= ctx.layoutInfo.lineNumbersWidth;
// On the line decorations
return request.fulfillMargin(4 /* MouseTargetType.GUTTER_LINE_DECORATIONS */, pos, res.range, detail);
}
return null;
}
static _hitTestViewLines(ctx, request) {
if (!ElementPath.isChildOfViewLines(request.targetPath)) {
return null;
}
if (ctx.isInTopPadding(request.mouseVerticalOffset)) {
return request.fulfillContentEmpty(new Position(1, 1), EMPTY_CONTENT_AFTER_LINES);
}
// Check if it is below any lines and any view zones
if (ctx.isAfterLines(request.mouseVerticalOffset) || ctx.isInBottomPadding(request.mouseVerticalOffset)) {
// This most likely indicates it happened after the last view-line
const lineCount = ctx.viewModel.getLineCount();
const maxLineColumn = ctx.viewModel.getLineMaxColumn(lineCount);
return request.fulfillContentEmpty(new Position(lineCount, maxLineColumn), EMPTY_CONTENT_AFTER_LINES);
}
// Check if we are hitting a view-line (can happen in the case of inline decorations on empty lines)
// See https://github.com/microsoft/vscode/issues/46942
if (ElementPath.isStrictChildOfViewLines(request.targetPath)) {
const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
if (ctx.viewModel.getLineLength(lineNumber) === 0) {
const lineWidth = ctx.getLineWidth(lineNumber);
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfillContentEmpty(new Position(lineNumber, 1), detail);
}
const lineWidth = ctx.getLineWidth(lineNumber);
if (request.mouseContentHorizontalOffset >= lineWidth) {
// TODO: This is wrong for RTL
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
const pos = new Position(lineNumber, ctx.viewModel.getLineMaxColumn(lineNumber));
return request.fulfillContentEmpty(pos, detail);
}
}
// Do the hit test (if not already done)
const hitTestResult = request.hitTestResult.value;
if (hitTestResult.type === 1 /* HitTestResultType.Content */) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText);
}
// We didn't hit content...
if (request.wouldBenefitFromHitTestTargetSwitch) {
// We actually hit something different... Give it one last change by trying again with this new target
request.switchToHitTestTarget();
return this._createMouseTarget(ctx, request);
}
// We have tried everything...
return request.fulfillUnknown();
}
static _hitTestMinimap(ctx, request) {
if (ElementPath.isChildOfMinimap(request.targetPath)) {
const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
const maxColumn = ctx.viewModel.getLineMaxColumn(possibleLineNumber);
return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn));
}
return null;
}
static _hitTestScrollbarSlider(ctx, request) {
if (ElementPath.isChildOfScrollableElement(request.targetPath)) {
if (request.target && request.target.nodeType === 1) {
const className = request.target.className;
if (className && /\b(slider|scrollbar)\b/.test(className)) {
const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
const maxColumn = ctx.viewModel.getLineMaxColumn(possibleLineNumber);
return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn));
}
}
}
return null;
}
static _hitTestScrollbar(ctx, request) {
// Is it the overview ruler?
// Is it a child of the scrollable element?
if (ElementPath.isChildOfScrollableElement(request.targetPath)) {
const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
const maxColumn = ctx.viewModel.getLineMaxColumn(possibleLineNumber);
return request.fulfillScrollbar(new Position(possibleLineNumber, maxColumn));
}
return null;
}
getMouseColumn(relativePos) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const mouseContentHorizontalOffset = this._context.viewLayout.getCurrentScrollLeft() + relativePos.x - layoutInfo.contentLeft;
return MouseTargetFactory._getMouseColumn(mouseContentHorizontalOffset, options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth);
}
static _getMouseColumn(mouseContentHorizontalOffset, typicalHalfwidthCharacterWidth) {
if (mouseContentHorizontalOffset < 0) {
return 1;
}
const chars = Math.round(mouseContentHorizontalOffset / typicalHalfwidthCharacterWidth);
return (chars + 1);
}
static createMouseTargetFromHitTestPosition(ctx, request, spanNode, pos, injectedText) {
const lineNumber = pos.lineNumber;
const column = pos.column;
const lineWidth = ctx.getLineWidth(lineNumber);
if (request.mouseContentHorizontalOffset > lineWidth) {
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfillContentEmpty(pos, detail);
}
const visibleRange = ctx.visibleRangeForPosition(lineNumber, column);
if (!visibleRange) {
return request.fulfillUnknown(pos);
}
const columnHorizontalOffset = visibleRange.left;
if (Math.abs(request.mouseContentHorizontalOffset - columnHorizontalOffset) < 1) {
return request.fulfillContentText(pos, null, { mightBeForeignElement: !!injectedText, injectedText });
}
const points = [];
points.push({ offset: visibleRange.left, column: column });
if (column > 1) {
const visibleRange = ctx.visibleRangeForPosition(lineNumber, column - 1);
if (visibleRange) {
points.push({ offset: visibleRange.left, column: column - 1 });
}
}
const lineMaxColumn = ctx.viewModel.getLineMaxColumn(lineNumber);
if (column < lineMaxColumn) {
const visibleRange = ctx.visibleRangeForPosition(lineNumber, column + 1);
if (visibleRange) {
points.push({ offset: visibleRange.left, column: column + 1 });
}
}
points.sort((a, b) => a.offset - b.offset);
const mouseCoordinates = request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode));
const spanNodeClientRect = spanNode.getBoundingClientRect();
const mouseIsOverSpanNode = (spanNodeClientRect.left <= mouseCoordinates.clientX && mouseCoordinates.clientX <= spanNodeClientRect.right);
let rng = null;
for (let i = 1; i < points.length; i++) {
const prev = points[i - 1];
const curr = points[i];
if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) {
rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column);
// See https://github.com/microsoft/vscode/issues/152819
// Due to the use of zwj, the browser's hit test result is skewed towards the left
// Here we try to correct that if the mouse horizontal offset is closer to the right than the left
const prevDelta = Math.abs(prev.offset - request.mouseContentHorizontalOffset);
const nextDelta = Math.abs(curr.offset - request.mouseContentHorizontalOffset);
pos = (prevDelta < nextDelta
? new Position(lineNumber, prev.column)
: new Position(lineNumber, curr.column));
break;
}
}
return request.fulfillContentText(pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText });
}
/**
* Most probably WebKit browsers and Edge
*/
static _doHitTestWithCaretRangeFromPoint(ctx, request) {
// In Chrome, especially on Linux it is possible to click between lines,
// so try to adjust the `hity` below so that it lands in the center of a line
const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
const lineStartVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber);
const lineEndVerticalOffset = lineStartVerticalOffset + ctx.lineHeight;
const isBelowLastLine = (lineNumber === ctx.viewModel.getLineCount()
&& request.mouseVerticalOffset > lineEndVerticalOffset);
if (!isBelowLastLine) {
const lineCenteredVerticalOffset = Math.floor((lineStartVerticalOffset + lineEndVerticalOffset) / 2);
let adjustedPageY = request.pos.y + (lineCenteredVerticalOffset - request.mouseVerticalOffset);
if (adjustedPageY <= request.editorPos.y) {
adjustedPageY = request.editorPos.y + 1;
}
if (adjustedPageY >= request.editorPos.y + request.editorPos.height) {
adjustedPageY = request.editorPos.y + request.editorPos.height - 1;
}
const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY);
const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates(dom.getWindow(ctx.viewDomNode)));
if (r.type === 1 /* HitTestResultType.Content */) {
return r;
}
}
// Also try to hit test without the adjustment (for the edge cases that we are near the top or bottom)
return this._actualDoHitTestWithCaretRangeFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode)));
}
static _actualDoHitTestWithCaretRangeFromPoint(ctx, coords) {
const shadowRoot = dom.getShadowRoot(ctx.viewDomNode);
let range;
if (shadowRoot) {
if (typeof shadowRoot.caretRangeFromPoint === 'undefined') {
range = shadowCaretRangeFromPoint(shadowRoot, coords.clientX, coords.clientY);
}
else {
range = shadowRoot.caretRangeFromPoint(coords.clientX, coords.clientY);
}
}
else {
range = ctx.viewDomNode.ownerDocument.caretRangeFromPoint(coords.clientX, coords.clientY);
}
if (!range || !range.startContainer) {
return new UnknownHitTestResult();
}
// Chrome always hits a TEXT_NODE, while Edge sometimes hits a token span
const startContainer = range.startContainer;
if (startContainer.nodeType === startContainer.TEXT_NODE) {
// startContainer is expected to be the token text
const parent1 = startContainer.parentNode; // expected to be the token span
const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span
const parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div
const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? parent3.className : null;
if (parent3ClassName === ViewLine.CLASS_NAME) {
return HitTestResult.createFromDOMInfo(ctx, parent1, range.startOffset);
}
else {
return new UnknownHitTestResult(startContainer.parentNode);
}
}
else if (startContainer.nodeType === startContainer.ELEMENT_NODE) {
// startContainer is expected to be the token span
const parent1 = startContainer.parentNode; // expected to be the view line container span
const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line div
const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? parent2.className : null;
if (parent2ClassName === ViewLine.CLASS_NAME) {
return HitTestResult.createFromDOMInfo(ctx, startContainer, startContainer.textContent.length);
}
else {
return new UnknownHitTestResult(startContainer);
}
}
return new UnknownHitTestResult();
}
/**
* Most probably Gecko
*/
static _doHitTestWithCaretPositionFromPoint(ctx, coords) {
const hitResult = ctx.viewDomNode.ownerDocument.caretPositionFromPoint(coords.clientX, coords.clientY);
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) {
// offsetNode is expected to be the token text
const parent1 = hitResult.offsetNode.parentNode; // expected to be the token span
const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span
const parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div
const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? parent3.className : null;
if (parent3ClassName === ViewLine.CLASS_NAME) {
return HitTestResult.createFromDOMInfo(ctx, hitResult.offsetNode.parentNode, hitResult.offset);
}
else {
return new UnknownHitTestResult(hitResult.offsetNode.parentNode);
}
}
// For inline decorations, Gecko sometimes returns the `<span>` of the line and the offset is the `<span>` with the inline decoration
// Some other times, it returns the `<span>` with the inline decoration
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.ELEMENT_NODE) {
const parent1 = hitResult.offsetNode.parentNode;
const parent1ClassName = parent1 && parent1.nodeType === parent1.ELEMENT_NODE ? parent1.className : null;
const parent2 = parent1 ? parent1.parentNode : null;
const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? parent2.className : null;
if (parent1ClassName === ViewLine.CLASS_NAME) {
// it returned the `<span>` of the line and the offset is the `<span>` with the inline decoration
const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)];
if (tokenSpan) {
return HitTestResult.createFromDOMInfo(ctx, tokenSpan, 0);
}
}
else if (parent2ClassName === ViewLine.CLASS_NAME) {
// it returned the `<span>` with the inline decoration
return HitTestResult.createFromDOMInfo(ctx, hitResult.offsetNode, 0);
}
}
return new UnknownHitTestResult(hitResult.offsetNode);
}
static _snapToSoftTabBoundary(position, viewModel) {
const lineContent = viewModel.getLineContent(position.lineNumber);
const { tabSize } = viewModel.model.getOptions();
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - 1, tabSize, 2 /* Direction.Nearest */);
if (newPosition !== -1) {
return new Position(position.lineNumber, newPosition + 1);
}
return position;
}
static doHitTest(ctx, request) {
let result = new UnknownHitTestResult();
if (typeof ctx.viewDomNode.ownerDocument.caretRangeFromPoint === 'function') {
result = this._doHitTestWithCaretRangeFromPoint(ctx, request);
}
else if (ctx.viewDomNode.ownerDocument.caretPositionFromPoint) {
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode)));
}
if (result.type === 1 /* HitTestResultType.Content */) {
const injectedText = ctx.viewModel.getInjectedTextAt(result.position);
const normalizedPosition = ctx.viewModel.normalizePosition(result.position, 2 /* PositionAffinity.None */);
if (injectedText || !normalizedPosition.equals(result.position)) {
result = new ContentHitTestResult(normalizedPosition, result.spanNode, injectedText);
}
}
return result;
}
}
function shadowCaretRangeFromPoint(shadowRoot, x, y) {
const range = document.createRange();
// Get the element under the point
let el = shadowRoot.elementFromPoint(x, y);
if (el !== null) {
// Get the last child of the element until its firstChild is a text node
// This assumes that the pointer is on the right of the line, out of the tokens
// and that we want to get the offset of the last token of the line
while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE && el.lastChild && el.lastChild.firstChild) {
el = el.lastChild;
}
// Grab its rect
const rect = el.getBoundingClientRect();
// And its font (the computed shorthand font property might be empty, see #3217)
const elWindow = dom.getWindow(el);
const fontStyle = elWindow.getComputedStyle(el, null).getPropertyValue('font-style');
const fontVariant = elWindow.getComputedStyle(el, null).getPropertyValue('font-variant');
const fontWeight = elWindow.getComputedStyle(el, null).getPropertyValue('font-weight');
const fontSize = elWindow.getComputedStyle(el, null).getPropertyValue('font-size');
const lineHeight = elWindow.getComputedStyle(el, null).getPropertyValue('line-height');
const fontFamily = elWindow.getComputedStyle(el, null).getPropertyValue('font-family');
const font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
// And also its txt content
const text = el.innerText;
// Position the pixel cursor at the left of the element
let pixelCursor = rect.left;
let offset = 0;
let step;
// If the point is on the right of the box put the cursor after the last character
if (x > rect.left + rect.width) {
offset = text.length;
}
else {
const charWidthReader = CharWidthReader.getInstance();
// Goes through all the characters of the innerText, and checks if the x of the point
// belongs to the character.
for (let i = 0; i < text.length + 1; i++) {
// The step is half the width of the character
step = charWidthReader.getCharWidth(text.charAt(i), font) / 2;
// Move to the center of the character
pixelCursor += step;
// If the x of the point is smaller that the position of the cursor, the point is over that character
if (x < pixelCursor) {
offset = i;
break;
}
// Move between the current character and the next
pixelCursor += step;
}
}
// Creates a range with the text node of the element and set the offset found
range.setStart(el.firstChild, offset);
range.setEnd(el.firstChild, offset);
}
return range;
}
class CharWidthReader {
static { this._INSTANCE = null; }
static getInstance() {
if (!CharWidthReader._INSTANCE) {
CharWidthReader._INSTANCE = new CharWidthReader();
}
return CharWidthReader._INSTANCE;
}
constructor() {
this._cache = {};
this._canvas = document.createElement('canvas');
}
getCharWidth(char, font) {
const cacheKey = char + font;
if (this._cache[cacheKey]) {
return this._cache[cacheKey];
}
const context = this._canvas.getContext('2d');
context.font = font;
const metrics = context.measureText(char);
const width = metrics.width;
this._cache[cacheKey] = width;
return width;
}
}
@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BrowserFeatures } from '../../../base/browser/canIUse.js';
import * as dom from '../../../base/browser/dom.js';
import { EventType, Gesture } from '../../../base/browser/touch.js';
import { mainWindow } from '../../../base/browser/window.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import * as platform from '../../../base/common/platform.js';
import { MouseHandler } from './mouseHandler.js';
import { TextAreaSyntethicEvents } from './textAreaInput.js';
import { EditorMouseEvent, EditorPointerEventFactory } from '../editorDom.js';
/**
* Currently only tested on iOS 13/ iPadOS.
*/
export class PointerEventHandler extends MouseHandler {
constructor(context, viewController, viewHelper) {
super(context, viewController, viewHelper);
this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e)));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e)));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e) => this._onContextMenu(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode), false)));
this._lastPointerType = 'mouse';
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e) => {
const pointerType = e.pointerType;
if (pointerType === 'mouse') {
this._lastPointerType = 'mouse';
return;
}
else if (pointerType === 'touch') {
this._lastPointerType = 'touch';
}
else {
this._lastPointerType = 'pen';
}
}));
// PonterEvents
const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode);
this._register(pointerEvents.onPointerMove(this.viewHelper.viewDomNode, (e) => this._onMouseMoveOverView(e)));
this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));
this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));
this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e, pointerId) => this._onMouseDown(e, pointerId)));
}
onTap(event) {
if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) {
return;
}
event.preventDefault();
this.viewHelper.focusTextArea();
this._dispatchGesture(event, /*inSelectionMode*/ false);
}
onChange(event) {
if (this._lastPointerType === 'touch') {
this._context.viewModel.viewLayout.deltaScrollNow(-event.translationX, -event.translationY);
}
if (this._lastPointerType === 'pen') {
this._dispatchGesture(event, /*inSelectionMode*/ true);
}
}
_dispatchGesture(event, inSelectionMode) {
const target = this._createMouseTargetForView(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false);
if (target.position) {
this.viewController.dispatchMouse({
position: target.position,
mouseColumn: target.position.column,
startedOnLineNumbers: false,
revealType: 1 /* NavigationCommandRevealType.Minimal */,
mouseDownCount: event.tapCount,
inSelectionMode,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
leftButton: false,
middleButton: false,
onInjectedText: target.type === 6 /* MouseTargetType.CONTENT_TEXT */ && target.detail.injectedText !== null
});
}
}
_onMouseDown(e, pointerId) {
if (e.browserEvent.pointerType === 'touch') {
return;
}
super._onMouseDown(e, pointerId);
}
}
class TouchHandler extends MouseHandler {
constructor(context, viewController, viewHelper) {
super(context, viewController, viewHelper);
this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e)));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e)));
this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e) => this._onContextMenu(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode), false)));
}
onTap(event) {
event.preventDefault();
this.viewHelper.focusTextArea();
const target = this._createMouseTargetForView(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false);
if (target.position) {
// Send the tap event also to the <textarea> (for input purposes)
const event = document.createEvent('CustomEvent');
event.initEvent(TextAreaSyntethicEvents.Tap, false, true);
this.viewHelper.dispatchTextAreaEvent(event);
this.viewController.moveTo(target.position, 1 /* NavigationCommandRevealType.Minimal */);
}
}
onChange(e) {
this._context.viewModel.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
}
}
export class PointerHandler extends Disposable {
constructor(context, viewController, viewHelper) {
super();
const isPhone = platform.isIOS || (platform.isAndroid && platform.isMobile);
if (isPhone && BrowserFeatures.pointerEvents) {
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
}
else if (mainWindow.TouchEvent) {
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
}
else {
this.handler = this._register(new MouseHandler(context, viewController, viewHelper));
}
}
getTargetAtClientPoint(clientX, clientY) {
return this.handler.getTargetAtClientPoint(clientX, clientY);
}
}
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .inputarea {
min-width: 0;
min-height: 0;
margin: 0;
padding: 0;
position: absolute;
outline: none !important;
resize: none;
border: none;
overflow: hidden;
color: transparent;
background-color: transparent;
z-index: -10;
}
/*.monaco-editor .inputarea {
position: fixed !important;
width: 800px !important;
height: 500px !important;
top: initial !important;
left: initial !important;
bottom: 0 !important;
right: 0 !important;
color: black !important;
background: white !important;
line-height: 15px !important;
font-size: 14px !important;
z-index: 10 !important;
}*/
.monaco-editor .inputarea.ime-input {
z-index: 10;
caret-color: var(--vscode-editorCursor-foreground);
color: var(--vscode-editor-foreground);
}
@@ -0,0 +1,777 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import './textAreaHandler.css';
import * as nls from '../../../nls.js';
import * as browser from '../../../base/browser/browser.js';
import { createFastDomNode } from '../../../base/browser/fastDomNode.js';
import * as platform from '../../../base/common/platform.js';
import * as strings from '../../../base/common/strings.js';
import { applyFontInfo } from '../config/domFontInfo.js';
import { CopyOptions, TextAreaInput, TextAreaWrapper } from './textAreaInput.js';
import { PagedScreenReaderStrategy, TextAreaState, _debugComposition } from './textAreaState.js';
import { PartFingerprints, ViewPart } from '../view/viewPart.js';
import { LineNumbersOverlay } from '../viewParts/lineNumbers/lineNumbers.js';
import { Margin } from '../viewParts/margin/margin.js';
import { EditorOptions } from '../../common/config/editorOptions.js';
import { getMapForWordSeparators } from '../../common/core/wordCharacterClassifier.js';
import { Position } from '../../common/core/position.js';
import { Range } from '../../common/core/range.js';
import { Selection } from '../../common/core/selection.js';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../base/browser/ui/mouseCursor/mouseCursor.js';
import { TokenizationRegistry } from '../../common/languages.js';
import { Color } from '../../../base/common/color.js';
import { IME } from '../../../base/common/ime.js';
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
class VisibleTextAreaData {
constructor(_context, modelLineNumber, distanceToModelLineStart, widthOfHiddenLineTextBefore, distanceToModelLineEnd) {
this._context = _context;
this.modelLineNumber = modelLineNumber;
this.distanceToModelLineStart = distanceToModelLineStart;
this.widthOfHiddenLineTextBefore = widthOfHiddenLineTextBefore;
this.distanceToModelLineEnd = distanceToModelLineEnd;
this._visibleTextAreaBrand = undefined;
this.startPosition = null;
this.endPosition = null;
this.visibleTextareaStart = null;
this.visibleTextareaEnd = null;
/**
* When doing composition, the currently composed text might be split up into
* multiple tokens, then merged again into a single token, etc. Here we attempt
* to keep the presentation of the <textarea> stable by using the previous used
* style if multiple tokens come into play. This avoids flickering.
*/
this._previousPresentation = null;
}
prepareRender(visibleRangeProvider) {
const startModelPosition = new Position(this.modelLineNumber, this.distanceToModelLineStart + 1);
const endModelPosition = new Position(this.modelLineNumber, this._context.viewModel.model.getLineMaxColumn(this.modelLineNumber) - this.distanceToModelLineEnd);
this.startPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(startModelPosition);
this.endPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(endModelPosition);
if (this.startPosition.lineNumber === this.endPosition.lineNumber) {
this.visibleTextareaStart = visibleRangeProvider.visibleRangeForPosition(this.startPosition);
this.visibleTextareaEnd = visibleRangeProvider.visibleRangeForPosition(this.endPosition);
}
else {
// TODO: what if the view positions are not on the same line?
this.visibleTextareaStart = null;
this.visibleTextareaEnd = null;
}
}
definePresentation(tokenPresentation) {
if (!this._previousPresentation) {
// To avoid flickering, once set, always reuse a presentation throughout the entire IME session
if (tokenPresentation) {
this._previousPresentation = tokenPresentation;
}
else {
this._previousPresentation = {
foreground: 1 /* ColorId.DefaultForeground */,
italic: false,
bold: false,
underline: false,
strikethrough: false,
};
}
}
return this._previousPresentation;
}
}
const canUseZeroSizeTextarea = (browser.isFirefox);
let TextAreaHandler = class TextAreaHandler extends ViewPart {
constructor(context, viewController, visibleRangeProvider, _keybindingService, _instantiationService) {
super(context);
this._keybindingService = _keybindingService;
this._instantiationService = _instantiationService;
this._primaryCursorPosition = new Position(1, 1);
this._primaryCursorVisibleRange = null;
this._viewController = viewController;
this._visibleRangeProvider = visibleRangeProvider;
this._scrollLeft = 0;
this._scrollTop = 0;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._setAccessibilityOptions(options);
this._contentLeft = layoutInfo.contentLeft;
this._contentWidth = layoutInfo.contentWidth;
this._contentHeight = layoutInfo.height;
this._fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._emptySelectionClipboard = options.get(37 /* EditorOption.emptySelectionClipboard */);
this._copyWithSyntaxHighlighting = options.get(25 /* EditorOption.copyWithSyntaxHighlighting */);
this._visibleTextArea = null;
this._selections = [new Selection(1, 1, 1, 1)];
this._modelSelections = [new Selection(1, 1, 1, 1)];
this._lastRenderPosition = null;
// Text Area (The focus will always be in the textarea when the cursor is blinking)
this.textArea = createFastDomNode(document.createElement('textarea'));
PartFingerprints.write(this.textArea, 7 /* PartFingerprint.TextArea */);
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off');
const { tabSize } = this._context.viewModel.model.getOptions();
this.textArea.domNode.style.tabSize = `${tabSize * this._fontInfo.spaceWidth}px`;
this.textArea.setAttribute('autocorrect', 'off');
this.textArea.setAttribute('autocapitalize', 'off');
this.textArea.setAttribute('autocomplete', 'off');
this.textArea.setAttribute('spellcheck', 'false');
this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
this.textArea.setAttribute('aria-required', options.get(5 /* EditorOption.ariaRequired */) ? 'true' : 'false');
this.textArea.setAttribute('tabindex', String(options.get(125 /* EditorOption.tabIndex */)));
this.textArea.setAttribute('role', 'textbox');
this.textArea.setAttribute('aria-roledescription', nls.localize('editor', "editor"));
this.textArea.setAttribute('aria-multiline', 'true');
this.textArea.setAttribute('aria-autocomplete', options.get(92 /* EditorOption.readOnly */) ? 'none' : 'both');
this._ensureReadOnlyAttribute();
this.textAreaCover = createFastDomNode(document.createElement('div'));
this.textAreaCover.setPosition('absolute');
const simpleModel = {
getLineCount: () => {
return this._context.viewModel.getLineCount();
},
getLineMaxColumn: (lineNumber) => {
return this._context.viewModel.getLineMaxColumn(lineNumber);
},
getValueInRange: (range, eol) => {
return this._context.viewModel.getValueInRange(range, eol);
},
getValueLengthInRange: (range, eol) => {
return this._context.viewModel.getValueLengthInRange(range, eol);
},
modifyPosition: (position, offset) => {
return this._context.viewModel.modifyPosition(position, offset);
}
};
const textAreaInputHost = {
getDataToCopy: () => {
const rawTextToCopy = this._context.viewModel.getPlainTextToCopy(this._modelSelections, this._emptySelectionClipboard, platform.isWindows);
const newLineCharacter = this._context.viewModel.model.getEOL();
const isFromEmptySelection = (this._emptySelectionClipboard && this._modelSelections.length === 1 && this._modelSelections[0].isEmpty());
const multicursorText = (Array.isArray(rawTextToCopy) ? rawTextToCopy : null);
const text = (Array.isArray(rawTextToCopy) ? rawTextToCopy.join(newLineCharacter) : rawTextToCopy);
let html = undefined;
let mode = null;
if (CopyOptions.forceCopyWithSyntaxHighlighting || (this._copyWithSyntaxHighlighting && text.length < 65536)) {
const richText = this._context.viewModel.getRichTextToCopy(this._modelSelections, this._emptySelectionClipboard);
if (richText) {
html = richText.html;
mode = richText.mode;
}
}
return {
isFromEmptySelection,
multicursorText,
text,
html,
mode
};
},
getScreenReaderContent: () => {
if (this._accessibilitySupport === 1 /* AccessibilitySupport.Disabled */) {
// We know for a fact that a screen reader is not attached
// On OSX, we write the character before the cursor to allow for "long-press" composition
// Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints
const selection = this._selections[0];
if (platform.isMacintosh && selection.isEmpty()) {
const position = selection.getStartPosition();
let textBefore = this._getWordBeforePosition(position);
if (textBefore.length === 0) {
textBefore = this._getCharacterBeforePosition(position);
}
if (textBefore.length > 0) {
return new TextAreaState(textBefore, textBefore.length, textBefore.length, Range.fromPositions(position), 0);
}
}
// on macOS, write current selection into textarea will allow system text services pick selected text,
// but we still want to limit the amount of text given Chromium handles very poorly text even of a few
// thousand chars
// (https://github.com/microsoft/vscode/issues/27799)
const LIMIT_CHARS = 500;
if (platform.isMacintosh && !selection.isEmpty() && simpleModel.getValueLengthInRange(selection, 0 /* EndOfLinePreference.TextDefined */) < LIMIT_CHARS) {
const text = simpleModel.getValueInRange(selection, 0 /* EndOfLinePreference.TextDefined */);
return new TextAreaState(text, 0, text.length, selection, 0);
}
// on Safari, document.execCommand('cut') and document.execCommand('copy') will just not work
// if the textarea has no content selected. So if there is an editor selection, ensure something
// is selected in the textarea.
if (browser.isSafari && !selection.isEmpty()) {
const placeholderText = 'vscode-placeholder';
return new TextAreaState(placeholderText, 0, placeholderText.length, null, undefined);
}
return TextAreaState.EMPTY;
}
if (browser.isAndroid) {
// when tapping in the editor on a word, Android enters composition mode.
// in the `compositionstart` event we cannot clear the textarea, because
// it then forgets to ever send a `compositionend`.
// we therefore only write the current word in the textarea
const selection = this._selections[0];
if (selection.isEmpty()) {
const position = selection.getStartPosition();
const [wordAtPosition, positionOffsetInWord] = this._getAndroidWordAtPosition(position);
if (wordAtPosition.length > 0) {
return new TextAreaState(wordAtPosition, positionOffsetInWord, positionOffsetInWord, Range.fromPositions(position), 0);
}
}
return TextAreaState.EMPTY;
}
return PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === 0 /* AccessibilitySupport.Unknown */);
},
deduceModelPosition: (viewAnchorPosition, deltaOffset, lineFeedCnt) => {
return this._context.viewModel.deduceModelPositionRelativeToViewPosition(viewAnchorPosition, deltaOffset, lineFeedCnt);
}
};
const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode));
this._textAreaInput = this._register(this._instantiationService.createInstance(TextAreaInput, textAreaInputHost, textAreaWrapper, platform.OS, {
isAndroid: browser.isAndroid,
isChrome: browser.isChrome,
isFirefox: browser.isFirefox,
isSafari: browser.isSafari,
}));
this._register(this._textAreaInput.onKeyDown((e) => {
this._viewController.emitKeyDown(e);
}));
this._register(this._textAreaInput.onKeyUp((e) => {
this._viewController.emitKeyUp(e);
}));
this._register(this._textAreaInput.onPaste((e) => {
let pasteOnNewLine = false;
let multicursorText = null;
let mode = null;
if (e.metadata) {
pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection);
multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null);
mode = e.metadata.mode;
}
this._viewController.paste(e.text, pasteOnNewLine, multicursorText, mode);
}));
this._register(this._textAreaInput.onCut(() => {
this._viewController.cut();
}));
this._register(this._textAreaInput.onType((e) => {
if (e.replacePrevCharCnt || e.replaceNextCharCnt || e.positionDelta) {
// must be handled through the new command
if (_debugComposition) {
console.log(` => compositionType: <<${e.text}>>, ${e.replacePrevCharCnt}, ${e.replaceNextCharCnt}, ${e.positionDelta}`);
}
this._viewController.compositionType(e.text, e.replacePrevCharCnt, e.replaceNextCharCnt, e.positionDelta);
}
else {
if (_debugComposition) {
console.log(` => type: <<${e.text}>>`);
}
this._viewController.type(e.text);
}
}));
this._register(this._textAreaInput.onSelectionChangeRequest((modelSelection) => {
this._viewController.setSelection(modelSelection);
}));
this._register(this._textAreaInput.onCompositionStart((e) => {
// The textarea might contain some content when composition starts.
//
// When we make the textarea visible, it always has a height of 1 line,
// so we don't need to worry too much about content on lines above or below
// the selection.
//
// However, the text on the current line needs to be made visible because
// some IME methods allow to move to other glyphs on the current line
// (by pressing arrow keys).
//
// (1) The textarea might contain only some parts of the current line,
// like the word before the selection. Also, the content inside the textarea
// can grow or shrink as composition occurs. We therefore anchor the textarea
// in terms of distance to a certain line start and line end.
//
// (2) Also, we should not make \t characters visible, because their rendering
// inside the <textarea> will not align nicely with our rendering. We therefore
// will hide (if necessary) some of the leading text on the current line.
const ta = this.textArea.domNode;
const modelSelection = this._modelSelections[0];
const { distanceToModelLineStart, widthOfHiddenTextBefore } = (() => {
// Find the text that is on the current line before the selection
const textBeforeSelection = ta.value.substring(0, Math.min(ta.selectionStart, ta.selectionEnd));
const lineFeedOffset1 = textBeforeSelection.lastIndexOf('\n');
const lineTextBeforeSelection = textBeforeSelection.substring(lineFeedOffset1 + 1);
// We now search to see if we should hide some part of it (if it contains \t)
const tabOffset1 = lineTextBeforeSelection.lastIndexOf('\t');
const desiredVisibleBeforeCharCount = lineTextBeforeSelection.length - tabOffset1 - 1;
const startModelPosition = modelSelection.getStartPosition();
const visibleBeforeCharCount = Math.min(startModelPosition.column - 1, desiredVisibleBeforeCharCount);
const distanceToModelLineStart = startModelPosition.column - 1 - visibleBeforeCharCount;
const hiddenLineTextBefore = lineTextBeforeSelection.substring(0, lineTextBeforeSelection.length - visibleBeforeCharCount);
const { tabSize } = this._context.viewModel.model.getOptions();
const widthOfHiddenTextBefore = measureText(this.textArea.domNode.ownerDocument, hiddenLineTextBefore, this._fontInfo, tabSize);
return { distanceToModelLineStart, widthOfHiddenTextBefore };
})();
const { distanceToModelLineEnd } = (() => {
// Find the text that is on the current line after the selection
const textAfterSelection = ta.value.substring(Math.max(ta.selectionStart, ta.selectionEnd));
const lineFeedOffset2 = textAfterSelection.indexOf('\n');
const lineTextAfterSelection = lineFeedOffset2 === -1 ? textAfterSelection : textAfterSelection.substring(0, lineFeedOffset2);
const tabOffset2 = lineTextAfterSelection.indexOf('\t');
const desiredVisibleAfterCharCount = (tabOffset2 === -1 ? lineTextAfterSelection.length : lineTextAfterSelection.length - tabOffset2 - 1);
const endModelPosition = modelSelection.getEndPosition();
const visibleAfterCharCount = Math.min(this._context.viewModel.model.getLineMaxColumn(endModelPosition.lineNumber) - endModelPosition.column, desiredVisibleAfterCharCount);
const distanceToModelLineEnd = this._context.viewModel.model.getLineMaxColumn(endModelPosition.lineNumber) - endModelPosition.column - visibleAfterCharCount;
return { distanceToModelLineEnd };
})();
// Scroll to reveal the location in the editor where composition occurs
this._context.viewModel.revealRange('keyboard', true, Range.fromPositions(this._selections[0].getStartPosition()), 0 /* viewEvents.VerticalRevealType.Simple */, 1 /* ScrollType.Immediate */);
this._visibleTextArea = new VisibleTextAreaData(this._context, modelSelection.startLineNumber, distanceToModelLineStart, widthOfHiddenTextBefore, distanceToModelLineEnd);
// We turn off wrapping if the <textarea> becomes visible for composition
this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off');
this._visibleTextArea.prepareRender(this._visibleRangeProvider);
this._render();
// Show the textarea
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ime-input`);
this._viewController.compositionStart();
this._context.viewModel.onCompositionStart();
}));
this._register(this._textAreaInput.onCompositionUpdate((e) => {
if (!this._visibleTextArea) {
return;
}
this._visibleTextArea.prepareRender(this._visibleRangeProvider);
this._render();
}));
this._register(this._textAreaInput.onCompositionEnd(() => {
this._visibleTextArea = null;
// We turn on wrapping as necessary if the <textarea> hides after composition
this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off');
this._render();
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
this._viewController.compositionEnd();
this._context.viewModel.onCompositionEnd();
}));
this._register(this._textAreaInput.onFocus(() => {
this._context.viewModel.setHasFocus(true);
}));
this._register(this._textAreaInput.onBlur(() => {
this._context.viewModel.setHasFocus(false);
}));
this._register(IME.onDidChange(() => {
this._ensureReadOnlyAttribute();
}));
}
writeScreenReaderContent(reason) {
this._textAreaInput.writeNativeTextAreaContent(reason);
}
dispose() {
super.dispose();
}
_getAndroidWordAtPosition(position) {
const ANDROID_WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\|;:",.<>/?';
const lineContent = this._context.viewModel.getLineContent(position.lineNumber);
const wordSeparators = getMapForWordSeparators(ANDROID_WORD_SEPARATORS, []);
let goingLeft = true;
let startColumn = position.column;
let goingRight = true;
let endColumn = position.column;
let distance = 0;
while (distance < 50 && (goingLeft || goingRight)) {
if (goingLeft && startColumn <= 1) {
goingLeft = false;
}
if (goingLeft) {
const charCode = lineContent.charCodeAt(startColumn - 2);
const charClass = wordSeparators.get(charCode);
if (charClass !== 0 /* WordCharacterClass.Regular */) {
goingLeft = false;
}
else {
startColumn--;
}
}
if (goingRight && endColumn > lineContent.length) {
goingRight = false;
}
if (goingRight) {
const charCode = lineContent.charCodeAt(endColumn - 1);
const charClass = wordSeparators.get(charCode);
if (charClass !== 0 /* WordCharacterClass.Regular */) {
goingRight = false;
}
else {
endColumn++;
}
}
distance++;
}
return [lineContent.substring(startColumn - 1, endColumn - 1), position.column - startColumn];
}
_getWordBeforePosition(position) {
const lineContent = this._context.viewModel.getLineContent(position.lineNumber);
const wordSeparators = getMapForWordSeparators(this._context.configuration.options.get(132 /* EditorOption.wordSeparators */), []);
let column = position.column;
let distance = 0;
while (column > 1) {
const charCode = lineContent.charCodeAt(column - 2);
const charClass = wordSeparators.get(charCode);
if (charClass !== 0 /* WordCharacterClass.Regular */ || distance > 50) {
return lineContent.substring(column - 1, position.column - 1);
}
distance++;
column--;
}
return lineContent.substring(0, position.column - 1);
}
_getCharacterBeforePosition(position) {
if (position.column > 1) {
const lineContent = this._context.viewModel.getLineContent(position.lineNumber);
const charBefore = lineContent.charAt(position.column - 2);
if (!strings.isHighSurrogate(charBefore.charCodeAt(0))) {
return charBefore;
}
}
return '';
}
_getAriaLabel(options) {
const accessibilitySupport = options.get(2 /* EditorOption.accessibilitySupport */);
if (accessibilitySupport === 1 /* AccessibilitySupport.Disabled */) {
const toggleKeybindingLabel = this._keybindingService.lookupKeybinding('editor.action.toggleScreenReaderAccessibilityMode')?.getAriaLabel();
const runCommandKeybindingLabel = this._keybindingService.lookupKeybinding('workbench.action.showCommands')?.getAriaLabel();
const keybindingEditorKeybindingLabel = this._keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings')?.getAriaLabel();
const editorNotAccessibleMessage = nls.localize('accessibilityModeOff', "The editor is not accessible at this time.");
if (toggleKeybindingLabel) {
return nls.localize('accessibilityOffAriaLabel', "{0} To enable screen reader optimized mode, use {1}", editorNotAccessibleMessage, toggleKeybindingLabel);
}
else if (runCommandKeybindingLabel) {
return nls.localize('accessibilityOffAriaLabelNoKb', "{0} To enable screen reader optimized mode, open the quick pick with {1} and run the command Toggle Screen Reader Accessibility Mode, which is currently not triggerable via keyboard.", editorNotAccessibleMessage, runCommandKeybindingLabel);
}
else if (keybindingEditorKeybindingLabel) {
return nls.localize('accessibilityOffAriaLabelNoKbs', "{0} Please assign a keybinding for the command Toggle Screen Reader Accessibility Mode by accessing the keybindings editor with {1} and run it.", editorNotAccessibleMessage, keybindingEditorKeybindingLabel);
}
else {
// SOS
return editorNotAccessibleMessage;
}
}
return options.get(4 /* EditorOption.ariaLabel */);
}
_setAccessibilityOptions(options) {
this._accessibilitySupport = options.get(2 /* EditorOption.accessibilitySupport */);
const accessibilityPageSize = options.get(3 /* EditorOption.accessibilityPageSize */);
if (this._accessibilitySupport === 2 /* AccessibilitySupport.Enabled */ && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) {
// If a screen reader is attached and the default value is not set we should automatically increase the page size to 500 for a better experience
this._accessibilityPageSize = 500;
}
else {
this._accessibilityPageSize = accessibilityPageSize;
}
// When wrapping is enabled and a screen reader might be attached,
// we will size the textarea to match the width used for wrapping points computation (see `domLineBreaksComputer.ts`).
// This is because screen readers will read the text in the textarea and we'd like that the
// wrapping points in the textarea match the wrapping points in the editor.
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const wrappingColumn = layoutInfo.wrappingColumn;
if (wrappingColumn !== -1 && this._accessibilitySupport !== 1 /* AccessibilitySupport.Disabled */) {
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._textAreaWrapping = true;
this._textAreaWidth = Math.round(wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth);
}
else {
this._textAreaWrapping = false;
this._textAreaWidth = (canUseZeroSizeTextarea ? 0 : 1);
}
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._setAccessibilityOptions(options);
this._contentLeft = layoutInfo.contentLeft;
this._contentWidth = layoutInfo.contentWidth;
this._contentHeight = layoutInfo.height;
this._fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._emptySelectionClipboard = options.get(37 /* EditorOption.emptySelectionClipboard */);
this._copyWithSyntaxHighlighting = options.get(25 /* EditorOption.copyWithSyntaxHighlighting */);
this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off');
const { tabSize } = this._context.viewModel.model.getOptions();
this.textArea.domNode.style.tabSize = `${tabSize * this._fontInfo.spaceWidth}px`;
this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
this.textArea.setAttribute('aria-required', options.get(5 /* EditorOption.ariaRequired */) ? 'true' : 'false');
this.textArea.setAttribute('tabindex', String(options.get(125 /* EditorOption.tabIndex */)));
if (e.hasChanged(34 /* EditorOption.domReadOnly */) || e.hasChanged(92 /* EditorOption.readOnly */)) {
this._ensureReadOnlyAttribute();
}
if (e.hasChanged(2 /* EditorOption.accessibilitySupport */)) {
this._textAreaInput.writeNativeTextAreaContent('strategy changed');
}
return true;
}
onCursorStateChanged(e) {
this._selections = e.selections.slice(0);
this._modelSelections = e.modelSelections.slice(0);
// We must update the <textarea> synchronously, otherwise long press IME on macos breaks.
// See https://github.com/microsoft/vscode/issues/165821
this._textAreaInput.writeNativeTextAreaContent('selection changed');
return true;
}
onDecorationsChanged(e) {
// true for inline decorations that can end up relayouting text
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
this._scrollLeft = e.scrollLeft;
this._scrollTop = e.scrollTop;
return true;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
// --- begin view API
isFocused() {
return this._textAreaInput.isFocused();
}
focusTextArea() {
this._textAreaInput.focusTextArea();
}
getLastRenderData() {
return this._lastRenderPosition;
}
setAriaOptions(options) {
if (options.activeDescendant) {
this.textArea.setAttribute('aria-haspopup', 'true');
this.textArea.setAttribute('aria-autocomplete', 'list');
this.textArea.setAttribute('aria-activedescendant', options.activeDescendant);
}
else {
this.textArea.setAttribute('aria-haspopup', 'false');
this.textArea.setAttribute('aria-autocomplete', 'both');
this.textArea.removeAttribute('aria-activedescendant');
}
if (options.role) {
this.textArea.setAttribute('role', options.role);
}
}
// --- end view API
_ensureReadOnlyAttribute() {
const options = this._context.configuration.options;
// When someone requests to disable IME, we set the "readonly" attribute on the <textarea>.
// This will prevent composition.
const useReadOnly = !IME.enabled || (options.get(34 /* EditorOption.domReadOnly */) && options.get(92 /* EditorOption.readOnly */));
if (useReadOnly) {
this.textArea.setAttribute('readonly', 'true');
}
else {
this.textArea.removeAttribute('readonly');
}
}
prepareRender(ctx) {
this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition);
this._visibleTextArea?.prepareRender(ctx);
}
render(ctx) {
this._textAreaInput.writeNativeTextAreaContent('render');
this._render();
}
_render() {
if (this._visibleTextArea) {
// The text area is visible for composition reasons
const visibleStart = this._visibleTextArea.visibleTextareaStart;
const visibleEnd = this._visibleTextArea.visibleTextareaEnd;
const startPosition = this._visibleTextArea.startPosition;
const endPosition = this._visibleTextArea.endPosition;
if (startPosition && endPosition && visibleStart && visibleEnd && visibleEnd.left >= this._scrollLeft && visibleStart.left <= this._scrollLeft + this._contentWidth) {
const top = (this._context.viewLayout.getVerticalOffsetForLineNumber(this._primaryCursorPosition.lineNumber) - this._scrollTop);
const lineCount = this._newlinecount(this.textArea.domNode.value.substr(0, this.textArea.domNode.selectionStart));
let scrollLeft = this._visibleTextArea.widthOfHiddenLineTextBefore;
let left = (this._contentLeft + visibleStart.left - this._scrollLeft);
// See https://github.com/microsoft/vscode/issues/141725#issuecomment-1050670841
// Here we are adding +1 to avoid flickering that might be caused by having a width that is too small.
// This could be caused by rounding errors that might only show up with certain font families.
// In other words, a pixel might be lost when doing something like
// `Math.round(end) - Math.round(start)`
// vs
// `Math.round(end - start)`
let width = visibleEnd.left - visibleStart.left + 1;
if (left < this._contentLeft) {
// the textarea would be rendered on top of the margin,
// so reduce its width. We use the same technique as
// for hiding text before
const delta = (this._contentLeft - left);
left += delta;
scrollLeft += delta;
width -= delta;
}
if (width > this._contentWidth) {
// the textarea would be wider than the content width,
// so reduce its width.
width = this._contentWidth;
}
// Try to render the textarea with the color/font style to match the text under it
const viewLineData = this._context.viewModel.getViewLineData(startPosition.lineNumber);
const startTokenIndex = viewLineData.tokens.findTokenIndexAtOffset(startPosition.column - 1);
const endTokenIndex = viewLineData.tokens.findTokenIndexAtOffset(endPosition.column - 1);
const textareaSpansSingleToken = (startTokenIndex === endTokenIndex);
const presentation = this._visibleTextArea.definePresentation((textareaSpansSingleToken ? viewLineData.tokens.getPresentation(startTokenIndex) : null));
this.textArea.domNode.scrollTop = lineCount * this._lineHeight;
this.textArea.domNode.scrollLeft = scrollLeft;
this._doRender({
lastRenderPosition: null,
top: top,
left: left,
width: width,
height: this._lineHeight,
useCover: false,
color: (TokenizationRegistry.getColorMap() || [])[presentation.foreground],
italic: presentation.italic,
bold: presentation.bold,
underline: presentation.underline,
strikethrough: presentation.strikethrough
});
}
return;
}
if (!this._primaryCursorVisibleRange) {
// The primary cursor is outside the viewport => place textarea to the top left
this._renderAtTopLeft();
return;
}
const left = this._contentLeft + this._primaryCursorVisibleRange.left - this._scrollLeft;
if (left < this._contentLeft || left > this._contentLeft + this._contentWidth) {
// cursor is outside the viewport
this._renderAtTopLeft();
return;
}
const top = this._context.viewLayout.getVerticalOffsetForLineNumber(this._selections[0].positionLineNumber) - this._scrollTop;
if (top < 0 || top > this._contentHeight) {
// cursor is outside the viewport
this._renderAtTopLeft();
return;
}
// The primary cursor is in the viewport (at least vertically) => place textarea on the cursor
if (platform.isMacintosh || this._accessibilitySupport === 2 /* AccessibilitySupport.Enabled */) {
// For the popup emoji input, we will make the text area as high as the line height
// We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers
this._doRender({
lastRenderPosition: this._primaryCursorPosition,
top,
left: this._textAreaWrapping ? this._contentLeft : left,
width: this._textAreaWidth,
height: this._lineHeight,
useCover: false
});
// In case the textarea contains a word, we're going to try to align the textarea's cursor
// with our cursor by scrolling the textarea as much as possible
this.textArea.domNode.scrollLeft = this._primaryCursorVisibleRange.left;
const lineCount = this._textAreaInput.textAreaState.newlineCountBeforeSelection ?? this._newlinecount(this.textArea.domNode.value.substr(0, this.textArea.domNode.selectionStart));
this.textArea.domNode.scrollTop = lineCount * this._lineHeight;
return;
}
this._doRender({
lastRenderPosition: this._primaryCursorPosition,
top: top,
left: this._textAreaWrapping ? this._contentLeft : left,
width: this._textAreaWidth,
height: (canUseZeroSizeTextarea ? 0 : 1),
useCover: false
});
}
_newlinecount(text) {
let result = 0;
let startIndex = -1;
do {
startIndex = text.indexOf('\n', startIndex + 1);
if (startIndex === -1) {
break;
}
result++;
} while (true);
return result;
}
_renderAtTopLeft() {
// (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea)
// specifically, when doing Korean IME, setting the textarea to 0x0 breaks IME badly.
this._doRender({
lastRenderPosition: null,
top: 0,
left: 0,
width: this._textAreaWidth,
height: (canUseZeroSizeTextarea ? 0 : 1),
useCover: true
});
}
_doRender(renderData) {
this._lastRenderPosition = renderData.lastRenderPosition;
const ta = this.textArea;
const tac = this.textAreaCover;
applyFontInfo(ta, this._fontInfo);
ta.setTop(renderData.top);
ta.setLeft(renderData.left);
ta.setWidth(renderData.width);
ta.setHeight(renderData.height);
ta.setColor(renderData.color ? Color.Format.CSS.formatHex(renderData.color) : '');
ta.setFontStyle(renderData.italic ? 'italic' : '');
if (renderData.bold) {
// fontWeight is also set by `applyFontInfo`, so only overwrite it if necessary
ta.setFontWeight('bold');
}
ta.setTextDecoration(`${renderData.underline ? ' underline' : ''}${renderData.strikethrough ? ' line-through' : ''}`);
tac.setTop(renderData.useCover ? renderData.top : 0);
tac.setLeft(renderData.useCover ? renderData.left : 0);
tac.setWidth(renderData.useCover ? renderData.width : 0);
tac.setHeight(renderData.useCover ? renderData.height : 0);
const options = this._context.configuration.options;
if (options.get(57 /* EditorOption.glyphMargin */)) {
tac.setClassName('monaco-editor-background textAreaCover ' + Margin.OUTER_CLASS_NAME);
}
else {
if (options.get(68 /* EditorOption.lineNumbers */).renderType !== 0 /* RenderLineNumbersType.Off */) {
tac.setClassName('monaco-editor-background textAreaCover ' + LineNumbersOverlay.CLASS_NAME);
}
else {
tac.setClassName('monaco-editor-background textAreaCover');
}
}
}
};
TextAreaHandler = __decorate([
__param(3, IKeybindingService),
__param(4, IInstantiationService)
], TextAreaHandler);
export { TextAreaHandler };
function measureText(targetDocument, text, fontInfo, tabSize) {
if (text.length === 0) {
return 0;
}
const container = targetDocument.createElement('div');
container.style.position = 'absolute';
container.style.top = '-50000px';
container.style.width = '50000px';
const regularDomNode = targetDocument.createElement('span');
applyFontInfo(regularDomNode, fontInfo);
regularDomNode.style.whiteSpace = 'pre'; // just like the textarea
regularDomNode.style.tabSize = `${tabSize * fontInfo.spaceWidth}px`; // just like the textarea
regularDomNode.append(text);
container.appendChild(regularDomNode);
targetDocument.body.appendChild(container);
const res = regularDomNode.offsetWidth;
container.remove();
return res;
}
@@ -0,0 +1,634 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as browser from '../../../base/browser/browser.js';
import * as dom from '../../../base/browser/dom.js';
import { DomEmitter } from '../../../base/browser/event.js';
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { inputLatency } from '../../../base/browser/performance.js';
import { RunOnceScheduler } from '../../../base/common/async.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
import { Mimes } from '../../../base/common/mime.js';
import * as strings from '../../../base/common/strings.js';
import { TextAreaState, _debugComposition } from './textAreaState.js';
import { Selection } from '../../common/core/selection.js';
import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js';
import { ILogService } from '../../../platform/log/common/log.js';
export var TextAreaSyntethicEvents;
(function (TextAreaSyntethicEvents) {
TextAreaSyntethicEvents.Tap = '-monaco-textarea-synthetic-tap';
})(TextAreaSyntethicEvents || (TextAreaSyntethicEvents = {}));
export const CopyOptions = {
forceCopyWithSyntaxHighlighting: false
};
/**
* Every time we write to the clipboard, we record a bit of extra metadata here.
* Every time we read from the cipboard, if the text matches our last written text,
* we can fetch the previous metadata.
*/
export class InMemoryClipboardMetadataManager {
static { this.INSTANCE = new InMemoryClipboardMetadataManager(); }
constructor() {
this._lastState = null;
}
set(lastCopiedValue, data) {
this._lastState = { lastCopiedValue, data };
}
get(pastedText) {
if (this._lastState && this._lastState.lastCopiedValue === pastedText) {
// match!
return this._lastState.data;
}
this._lastState = null;
return null;
}
}
class CompositionContext {
constructor() {
this._lastTypeTextLength = 0;
}
handleCompositionUpdate(text) {
text = text || '';
const typeInput = {
text: text,
replacePrevCharCnt: this._lastTypeTextLength,
replaceNextCharCnt: 0,
positionDelta: 0
};
this._lastTypeTextLength = text.length;
return typeInput;
}
}
/**
* Writes screen reader content to the textarea and is able to analyze its input events to generate:
* - onCut
* - onPaste
* - onType
*
* Composition events are generated for presentation purposes (composition input is reflected in onType).
*/
let TextAreaInput = class TextAreaInput extends Disposable {
get textAreaState() {
return this._textAreaState;
}
constructor(_host, _textArea, _OS, _browser, _accessibilityService, _logService) {
super();
this._host = _host;
this._textArea = _textArea;
this._OS = _OS;
this._browser = _browser;
this._accessibilityService = _accessibilityService;
this._logService = _logService;
this._onFocus = this._register(new Emitter());
this.onFocus = this._onFocus.event;
this._onBlur = this._register(new Emitter());
this.onBlur = this._onBlur.event;
this._onKeyDown = this._register(new Emitter());
this.onKeyDown = this._onKeyDown.event;
this._onKeyUp = this._register(new Emitter());
this.onKeyUp = this._onKeyUp.event;
this._onCut = this._register(new Emitter());
this.onCut = this._onCut.event;
this._onPaste = this._register(new Emitter());
this.onPaste = this._onPaste.event;
this._onType = this._register(new Emitter());
this.onType = this._onType.event;
this._onCompositionStart = this._register(new Emitter());
this.onCompositionStart = this._onCompositionStart.event;
this._onCompositionUpdate = this._register(new Emitter());
this.onCompositionUpdate = this._onCompositionUpdate.event;
this._onCompositionEnd = this._register(new Emitter());
this.onCompositionEnd = this._onCompositionEnd.event;
this._onSelectionChangeRequest = this._register(new Emitter());
this.onSelectionChangeRequest = this._onSelectionChangeRequest.event;
this._asyncFocusGainWriteScreenReaderContent = this._register(new MutableDisposable());
this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0));
this._textAreaState = TextAreaState.EMPTY;
this._selectionChangeListener = null;
if (this._accessibilityService.isScreenReaderOptimized()) {
this.writeNativeTextAreaContent('ctor');
}
this._register(Event.runAndSubscribe(this._accessibilityService.onDidChangeScreenReaderOptimized, () => {
if (this._accessibilityService.isScreenReaderOptimized() && !this._asyncFocusGainWriteScreenReaderContent.value) {
this._asyncFocusGainWriteScreenReaderContent.value = this._register(new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0));
}
else {
this._asyncFocusGainWriteScreenReaderContent.clear();
}
}));
this._hasFocus = false;
this._currentComposition = null;
let lastKeyDown = null;
this._register(this._textArea.onKeyDown((_e) => {
const e = new StandardKeyboardEvent(_e);
if (e.keyCode === 114 /* KeyCode.KEY_IN_COMPOSITION */
|| (this._currentComposition && e.keyCode === 1 /* KeyCode.Backspace */)) {
// Stop propagation for keyDown events if the IME is processing key input
e.stopPropagation();
}
if (e.equals(9 /* KeyCode.Escape */)) {
// Prevent default always for `Esc`, otherwise it will generate a keypress
// See https://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx
e.preventDefault();
}
lastKeyDown = e;
this._onKeyDown.fire(e);
}));
this._register(this._textArea.onKeyUp((_e) => {
const e = new StandardKeyboardEvent(_e);
this._onKeyUp.fire(e);
}));
this._register(this._textArea.onCompositionStart((e) => {
if (_debugComposition) {
console.log(`[compositionstart]`, e);
}
const currentComposition = new CompositionContext();
if (this._currentComposition) {
// simply reset the composition context
this._currentComposition = currentComposition;
return;
}
this._currentComposition = currentComposition;
if (this._OS === 2 /* OperatingSystem.Macintosh */
&& lastKeyDown
&& lastKeyDown.equals(114 /* KeyCode.KEY_IN_COMPOSITION */)
&& this._textAreaState.selectionStart === this._textAreaState.selectionEnd
&& this._textAreaState.selectionStart > 0
&& this._textAreaState.value.substr(this._textAreaState.selectionStart - 1, 1) === e.data
&& (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft')) {
// Handling long press case on Chromium/Safari macOS + arrow key => pretend the character was selected
if (_debugComposition) {
console.log(`[compositionstart] Handling long press case on macOS + arrow key`, e);
}
// Pretend the previous character was composed (in order to get it removed by subsequent compositionupdate events)
currentComposition.handleCompositionUpdate('x');
this._onCompositionStart.fire({ data: e.data });
return;
}
if (this._browser.isAndroid) {
// when tapping on the editor, Android enters composition mode to edit the current word
// so we cannot clear the textarea on Android and we must pretend the current word was selected
this._onCompositionStart.fire({ data: e.data });
return;
}
this._onCompositionStart.fire({ data: e.data });
}));
this._register(this._textArea.onCompositionUpdate((e) => {
if (_debugComposition) {
console.log(`[compositionupdate]`, e);
}
const currentComposition = this._currentComposition;
if (!currentComposition) {
// should not be possible to receive a 'compositionupdate' without a 'compositionstart'
return;
}
if (this._browser.isAndroid) {
// On Android, the data sent with the composition update event is unusable.
// For example, if the cursor is in the middle of a word like Mic|osoft
// and Microsoft is chosen from the keyboard's suggestions, the e.data will contain "Microsoft".
// This is not really usable because it doesn't tell us where the edit began and where it ended.
const newState = TextAreaState.readFromTextArea(this._textArea, this._textAreaState);
const typeInput = TextAreaState.deduceAndroidCompositionInput(this._textAreaState, newState);
this._textAreaState = newState;
this._onType.fire(typeInput);
this._onCompositionUpdate.fire(e);
return;
}
const typeInput = currentComposition.handleCompositionUpdate(e.data);
this._textAreaState = TextAreaState.readFromTextArea(this._textArea, this._textAreaState);
this._onType.fire(typeInput);
this._onCompositionUpdate.fire(e);
}));
this._register(this._textArea.onCompositionEnd((e) => {
if (_debugComposition) {
console.log(`[compositionend]`, e);
}
const currentComposition = this._currentComposition;
if (!currentComposition) {
// https://github.com/microsoft/monaco-editor/issues/1663
// On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data
return;
}
this._currentComposition = null;
if (this._browser.isAndroid) {
// On Android, the data sent with the composition update event is unusable.
// For example, if the cursor is in the middle of a word like Mic|osoft
// and Microsoft is chosen from the keyboard's suggestions, the e.data will contain "Microsoft".
// This is not really usable because it doesn't tell us where the edit began and where it ended.
const newState = TextAreaState.readFromTextArea(this._textArea, this._textAreaState);
const typeInput = TextAreaState.deduceAndroidCompositionInput(this._textAreaState, newState);
this._textAreaState = newState;
this._onType.fire(typeInput);
this._onCompositionEnd.fire();
return;
}
const typeInput = currentComposition.handleCompositionUpdate(e.data);
this._textAreaState = TextAreaState.readFromTextArea(this._textArea, this._textAreaState);
this._onType.fire(typeInput);
this._onCompositionEnd.fire();
}));
this._register(this._textArea.onInput((e) => {
if (_debugComposition) {
console.log(`[input]`, e);
}
// Pretend here we touched the text area, as the `input` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received input event');
if (this._currentComposition) {
return;
}
const newState = TextAreaState.readFromTextArea(this._textArea, this._textAreaState);
const typeInput = TextAreaState.deduceInput(this._textAreaState, newState, /*couldBeEmojiInput*/ this._OS === 2 /* OperatingSystem.Macintosh */);
if (typeInput.replacePrevCharCnt === 0 && typeInput.text.length === 1) {
// one character was typed
if (strings.isHighSurrogate(typeInput.text.charCodeAt(0))
|| typeInput.text.charCodeAt(0) === 0x7f /* Delete */) {
// Ignore invalid input but keep it around for next time
return;
}
}
this._textAreaState = newState;
if (typeInput.text !== ''
|| typeInput.replacePrevCharCnt !== 0
|| typeInput.replaceNextCharCnt !== 0
|| typeInput.positionDelta !== 0) {
this._onType.fire(typeInput);
}
}));
// --- Clipboard operations
this._register(this._textArea.onCut((e) => {
// Pretend here we touched the text area, as the `cut` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received cut event');
this._ensureClipboardGetsEditorSelection(e);
this._asyncTriggerCut.schedule();
}));
this._register(this._textArea.onCopy((e) => {
this._ensureClipboardGetsEditorSelection(e);
}));
this._register(this._textArea.onPaste((e) => {
// Pretend here we touched the text area, as the `paste` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received paste event');
e.preventDefault();
if (!e.clipboardData) {
return;
}
let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);
if (!text) {
return;
}
// try the in-memory store
metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);
this._onPaste.fire({
text: text,
metadata: metadata
});
}));
this._register(this._textArea.onFocus(() => {
const hadFocus = this._hasFocus;
this._setHasFocus(true);
if (this._accessibilityService.isScreenReaderOptimized() && this._browser.isSafari && !hadFocus && this._hasFocus) {
// When "tabbing into" the textarea, immediately after dispatching the 'focus' event,
// Safari will always move the selection at offset 0 in the textarea
if (!this._asyncFocusGainWriteScreenReaderContent.value) {
this._asyncFocusGainWriteScreenReaderContent.value = new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0);
}
this._asyncFocusGainWriteScreenReaderContent.value.schedule();
}
}));
this._register(this._textArea.onBlur(() => {
if (this._currentComposition) {
// See https://github.com/microsoft/vscode/issues/112621
// where compositionend is not triggered when the editor
// is taken off-dom during a composition
// Clear the flag to be able to write to the textarea
this._currentComposition = null;
// Clear the textarea to avoid an unwanted cursor type
this.writeNativeTextAreaContent('blurWithoutCompositionEnd');
// Fire artificial composition end
this._onCompositionEnd.fire();
}
this._setHasFocus(false);
}));
this._register(this._textArea.onSyntheticTap(() => {
if (this._browser.isAndroid && this._currentComposition) {
// on Android, tapping does not cancel the current composition, so the
// textarea is stuck showing the old composition
// Clear the flag to be able to write to the textarea
this._currentComposition = null;
// Clear the textarea to avoid an unwanted cursor type
this.writeNativeTextAreaContent('tapWithoutCompositionEnd');
// Fire artificial composition end
this._onCompositionEnd.fire();
}
}));
}
_installSelectionChangeListener() {
// See https://github.com/microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256
// When using a Braille display, it is possible for users to reposition the
// system caret. This is reflected in Chrome as a `selectionchange` event.
//
// The `selectionchange` event appears to be emitted under numerous other circumstances,
// so it is quite a challenge to distinguish a `selectionchange` coming in from a user
// using a Braille display from all the other cases.
//
// The problems with the `selectionchange` event are:
// * the event is emitted when the textarea is focused programmatically -- textarea.focus()
// * the event is emitted when the selection is changed in the textarea programmatically -- textarea.setSelectionRange(...)
// * the event is emitted when the value of the textarea is changed programmatically -- textarea.value = '...'
// * the event is emitted when tabbing into the textarea
// * the event is emitted asynchronously (sometimes with a delay as high as a few tens of ms)
// * the event sometimes comes in bursts for a single logical textarea operation
// `selectionchange` events often come multiple times for a single logical change
// so throttle multiple `selectionchange` events that burst in a short period of time.
let previousSelectionChangeEventTime = 0;
return dom.addDisposableListener(this._textArea.ownerDocument, 'selectionchange', (e) => {
inputLatency.onSelectionChange();
if (!this._hasFocus) {
return;
}
if (this._currentComposition) {
return;
}
if (!this._browser.isChrome) {
// Support only for Chrome until testing happens on other browsers
return;
}
const now = Date.now();
const delta1 = now - previousSelectionChangeEventTime;
previousSelectionChangeEventTime = now;
if (delta1 < 5) {
// received another `selectionchange` event within 5ms of the previous `selectionchange` event
// => ignore it
return;
}
const delta2 = now - this._textArea.getIgnoreSelectionChangeTime();
this._textArea.resetSelectionChangeTime();
if (delta2 < 100) {
// received a `selectionchange` event within 100ms since we touched the textarea
// => ignore it, since we caused it
return;
}
if (!this._textAreaState.selection) {
// Cannot correlate a position in the textarea with a position in the editor...
return;
}
const newValue = this._textArea.getValue();
if (this._textAreaState.value !== newValue) {
// Cannot correlate a position in the textarea with a position in the editor...
return;
}
const newSelectionStart = this._textArea.getSelectionStart();
const newSelectionEnd = this._textArea.getSelectionEnd();
if (this._textAreaState.selectionStart === newSelectionStart && this._textAreaState.selectionEnd === newSelectionEnd) {
// Nothing to do...
return;
}
const _newSelectionStartPosition = this._textAreaState.deduceEditorPosition(newSelectionStart);
const newSelectionStartPosition = this._host.deduceModelPosition(_newSelectionStartPosition[0], _newSelectionStartPosition[1], _newSelectionStartPosition[2]);
const _newSelectionEndPosition = this._textAreaState.deduceEditorPosition(newSelectionEnd);
const newSelectionEndPosition = this._host.deduceModelPosition(_newSelectionEndPosition[0], _newSelectionEndPosition[1], _newSelectionEndPosition[2]);
const newSelection = new Selection(newSelectionStartPosition.lineNumber, newSelectionStartPosition.column, newSelectionEndPosition.lineNumber, newSelectionEndPosition.column);
this._onSelectionChangeRequest.fire(newSelection);
});
}
dispose() {
super.dispose();
if (this._selectionChangeListener) {
this._selectionChangeListener.dispose();
this._selectionChangeListener = null;
}
}
focusTextArea() {
// Setting this._hasFocus and writing the screen reader content
// will result in a focus() and setSelectionRange() in the textarea
this._setHasFocus(true);
// If the editor is off DOM, focus cannot be really set, so let's double check that we have managed to set the focus
this.refreshFocusState();
}
isFocused() {
return this._hasFocus;
}
refreshFocusState() {
this._setHasFocus(this._textArea.hasFocus());
}
_setHasFocus(newHasFocus) {
if (this._hasFocus === newHasFocus) {
// no change
return;
}
this._hasFocus = newHasFocus;
if (this._selectionChangeListener) {
this._selectionChangeListener.dispose();
this._selectionChangeListener = null;
}
if (this._hasFocus) {
this._selectionChangeListener = this._installSelectionChangeListener();
}
if (this._hasFocus) {
this.writeNativeTextAreaContent('focusgain');
}
if (this._hasFocus) {
this._onFocus.fire();
}
else {
this._onBlur.fire();
}
}
_setAndWriteTextAreaState(reason, textAreaState) {
if (!this._hasFocus) {
textAreaState = textAreaState.collapseSelection();
}
textAreaState.writeToTextArea(reason, this._textArea, this._hasFocus);
this._textAreaState = textAreaState;
}
writeNativeTextAreaContent(reason) {
if ((!this._accessibilityService.isScreenReaderOptimized() && reason === 'render') || this._currentComposition) {
// Do not write to the text on render unless a screen reader is being used #192278
// Do not write to the text area when doing composition
return;
}
this._logService.trace(`writeTextAreaState(reason: ${reason})`);
this._setAndWriteTextAreaState(reason, this._host.getScreenReaderContent());
}
_ensureClipboardGetsEditorSelection(e) {
const dataToCopy = this._host.getDataToCopy();
const storedMetadata = {
version: 1,
isFromEmptySelection: dataToCopy.isFromEmptySelection,
multicursorText: dataToCopy.multicursorText,
mode: dataToCopy.mode
};
InMemoryClipboardMetadataManager.INSTANCE.set(
// When writing "LINE\r\n" to the clipboard and then pasting,
// Firefox pastes "LINE\n", so let's work around this quirk
(this._browser.isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), storedMetadata);
e.preventDefault();
if (e.clipboardData) {
ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata);
}
}
};
TextAreaInput = __decorate([
__param(4, IAccessibilityService),
__param(5, ILogService)
], TextAreaInput);
export { TextAreaInput };
export const ClipboardEventUtils = {
getTextData(clipboardData) {
const text = clipboardData.getData(Mimes.text);
let metadata = null;
const rawmetadata = clipboardData.getData('vscode-editor-data');
if (typeof rawmetadata === 'string') {
try {
metadata = JSON.parse(rawmetadata);
if (metadata.version !== 1) {
metadata = null;
}
}
catch (err) {
// no problem!
}
}
if (text.length === 0 && metadata === null && clipboardData.files.length > 0) {
// no textual data pasted, generate text from file names
const files = Array.prototype.slice.call(clipboardData.files, 0);
return [files.map(file => file.name).join('\n'), null];
}
return [text, metadata];
},
setTextData(clipboardData, text, html, metadata) {
clipboardData.setData(Mimes.text, text);
if (typeof html === 'string') {
clipboardData.setData('text/html', html);
}
clipboardData.setData('vscode-editor-data', JSON.stringify(metadata));
}
};
export class TextAreaWrapper extends Disposable {
get ownerDocument() {
return this._actual.ownerDocument;
}
constructor(_actual) {
super();
this._actual = _actual;
this.onKeyDown = this._register(new DomEmitter(this._actual, 'keydown')).event;
this.onKeyUp = this._register(new DomEmitter(this._actual, 'keyup')).event;
this.onCompositionStart = this._register(new DomEmitter(this._actual, 'compositionstart')).event;
this.onCompositionUpdate = this._register(new DomEmitter(this._actual, 'compositionupdate')).event;
this.onCompositionEnd = this._register(new DomEmitter(this._actual, 'compositionend')).event;
this.onBeforeInput = this._register(new DomEmitter(this._actual, 'beforeinput')).event;
this.onInput = this._register(new DomEmitter(this._actual, 'input')).event;
this.onCut = this._register(new DomEmitter(this._actual, 'cut')).event;
this.onCopy = this._register(new DomEmitter(this._actual, 'copy')).event;
this.onPaste = this._register(new DomEmitter(this._actual, 'paste')).event;
this.onFocus = this._register(new DomEmitter(this._actual, 'focus')).event;
this.onBlur = this._register(new DomEmitter(this._actual, 'blur')).event;
this._onSyntheticTap = this._register(new Emitter());
this.onSyntheticTap = this._onSyntheticTap.event;
this._ignoreSelectionChangeTime = 0;
this._register(this.onKeyDown(() => inputLatency.onKeyDown()));
this._register(this.onBeforeInput(() => inputLatency.onBeforeInput()));
this._register(this.onInput(() => inputLatency.onInput()));
this._register(this.onKeyUp(() => inputLatency.onKeyUp()));
this._register(dom.addDisposableListener(this._actual, TextAreaSyntethicEvents.Tap, () => this._onSyntheticTap.fire()));
}
hasFocus() {
const shadowRoot = dom.getShadowRoot(this._actual);
if (shadowRoot) {
return shadowRoot.activeElement === this._actual;
}
else if (this._actual.isConnected) {
return dom.getActiveElement() === this._actual;
}
else {
return false;
}
}
setIgnoreSelectionChangeTime(reason) {
this._ignoreSelectionChangeTime = Date.now();
}
getIgnoreSelectionChangeTime() {
return this._ignoreSelectionChangeTime;
}
resetSelectionChangeTime() {
this._ignoreSelectionChangeTime = 0;
}
getValue() {
// console.log('current value: ' + this._textArea.value);
return this._actual.value;
}
setValue(reason, value) {
const textArea = this._actual;
if (textArea.value === value) {
// No change
return;
}
// console.log('reason: ' + reason + ', current value: ' + textArea.value + ' => new value: ' + value);
this.setIgnoreSelectionChangeTime('setValue');
textArea.value = value;
}
getSelectionStart() {
return this._actual.selectionDirection === 'backward' ? this._actual.selectionEnd : this._actual.selectionStart;
}
getSelectionEnd() {
return this._actual.selectionDirection === 'backward' ? this._actual.selectionStart : this._actual.selectionEnd;
}
setSelectionRange(reason, selectionStart, selectionEnd) {
const textArea = this._actual;
let activeElement = null;
const shadowRoot = dom.getShadowRoot(textArea);
if (shadowRoot) {
activeElement = shadowRoot.activeElement;
}
else {
activeElement = dom.getActiveElement();
}
const activeWindow = dom.getWindow(activeElement);
const currentIsFocused = (activeElement === textArea);
const currentSelectionStart = textArea.selectionStart;
const currentSelectionEnd = textArea.selectionEnd;
if (currentIsFocused && currentSelectionStart === selectionStart && currentSelectionEnd === selectionEnd) {
// No change
// Firefox iframe bug https://github.com/microsoft/monaco-editor/issues/643#issuecomment-367871377
if (browser.isFirefox && activeWindow.parent !== activeWindow) {
textArea.focus();
}
return;
}
// console.log('reason: ' + reason + ', setSelectionRange: ' + selectionStart + ' -> ' + selectionEnd);
if (currentIsFocused) {
// No need to focus, only need to change the selection range
this.setIgnoreSelectionChangeTime('setSelectionRange');
textArea.setSelectionRange(selectionStart, selectionEnd);
if (browser.isFirefox && activeWindow.parent !== activeWindow) {
textArea.focus();
}
return;
}
// If the focus is outside the textarea, browsers will try really hard to reveal the textarea.
// Here, we try to undo the browser's desperate reveal.
try {
const scrollState = dom.saveParentsScrollTop(textArea);
this.setIgnoreSelectionChangeTime('setSelectionRange');
textArea.focus();
textArea.setSelectionRange(selectionStart, selectionEnd);
dom.restoreParentsScrollTop(textArea, scrollState);
}
catch (e) {
// Sometimes IE throws when setting selection (e.g. textarea is off-DOM)
}
}
}
@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from '../../../base/common/strings.js';
import { Range } from '../../common/core/range.js';
export const _debugComposition = false;
export class TextAreaState {
static { this.EMPTY = new TextAreaState('', 0, 0, null, undefined); }
constructor(value,
/** the offset where selection starts inside `value` */
selectionStart,
/** the offset where selection ends inside `value` */
selectionEnd,
/** the editor range in the view coordinate system that matches the selection inside `value` */
selection,
/** the visible line count (wrapped, not necessarily matching \n characters) for the text in `value` before `selectionStart` */
newlineCountBeforeSelection) {
this.value = value;
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
this.selection = selection;
this.newlineCountBeforeSelection = newlineCountBeforeSelection;
}
toString() {
return `[ <${this.value}>, selectionStart: ${this.selectionStart}, selectionEnd: ${this.selectionEnd}]`;
}
static readFromTextArea(textArea, previousState) {
const value = textArea.getValue();
const selectionStart = textArea.getSelectionStart();
const selectionEnd = textArea.getSelectionEnd();
let newlineCountBeforeSelection = undefined;
if (previousState) {
const valueBeforeSelectionStart = value.substring(0, selectionStart);
const previousValueBeforeSelectionStart = previousState.value.substring(0, previousState.selectionStart);
if (valueBeforeSelectionStart === previousValueBeforeSelectionStart) {
newlineCountBeforeSelection = previousState.newlineCountBeforeSelection;
}
}
return new TextAreaState(value, selectionStart, selectionEnd, null, newlineCountBeforeSelection);
}
collapseSelection() {
if (this.selectionStart === this.value.length) {
return this;
}
return new TextAreaState(this.value, this.value.length, this.value.length, null, undefined);
}
writeToTextArea(reason, textArea, select) {
if (_debugComposition) {
console.log(`writeToTextArea ${reason}: ${this.toString()}`);
}
textArea.setValue(reason, this.value);
if (select) {
textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd);
}
}
deduceEditorPosition(offset) {
if (offset <= this.selectionStart) {
const str = this.value.substring(offset, this.selectionStart);
return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str, -1);
}
if (offset >= this.selectionEnd) {
const str = this.value.substring(this.selectionEnd, offset);
return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str, 1);
}
const str1 = this.value.substring(this.selectionStart, offset);
if (str1.indexOf(String.fromCharCode(8230)) === -1) {
return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str1, 1);
}
const str2 = this.value.substring(offset, this.selectionEnd);
return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str2, -1);
}
_finishDeduceEditorPosition(anchor, deltaText, signum) {
let lineFeedCnt = 0;
let lastLineFeedIndex = -1;
while ((lastLineFeedIndex = deltaText.indexOf('\n', lastLineFeedIndex + 1)) !== -1) {
lineFeedCnt++;
}
return [anchor, signum * deltaText.length, lineFeedCnt];
}
static deduceInput(previousState, currentState, couldBeEmojiInput) {
if (!previousState) {
// This is the EMPTY state
return {
text: '',
replacePrevCharCnt: 0,
replaceNextCharCnt: 0,
positionDelta: 0
};
}
if (_debugComposition) {
console.log('------------------------deduceInput');
console.log(`PREVIOUS STATE: ${previousState.toString()}`);
console.log(`CURRENT STATE: ${currentState.toString()}`);
}
const prefixLength = Math.min(strings.commonPrefixLength(previousState.value, currentState.value), previousState.selectionStart, currentState.selectionStart);
const suffixLength = Math.min(strings.commonSuffixLength(previousState.value, currentState.value), previousState.value.length - previousState.selectionEnd, currentState.value.length - currentState.selectionEnd);
const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength);
const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength);
const previousSelectionStart = previousState.selectionStart - prefixLength;
const previousSelectionEnd = previousState.selectionEnd - prefixLength;
const currentSelectionStart = currentState.selectionStart - prefixLength;
const currentSelectionEnd = currentState.selectionEnd - prefixLength;
if (_debugComposition) {
console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`);
console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`);
}
if (currentSelectionStart === currentSelectionEnd) {
// no current selection
const replacePreviousCharacters = (previousState.selectionStart - prefixLength);
if (_debugComposition) {
console.log(`REMOVE PREVIOUS: ${replacePreviousCharacters} chars`);
}
return {
text: currentValue,
replacePrevCharCnt: replacePreviousCharacters,
replaceNextCharCnt: 0,
positionDelta: 0
};
}
// there is a current selection => composition case
const replacePreviousCharacters = previousSelectionEnd - previousSelectionStart;
return {
text: currentValue,
replacePrevCharCnt: replacePreviousCharacters,
replaceNextCharCnt: 0,
positionDelta: 0
};
}
static deduceAndroidCompositionInput(previousState, currentState) {
if (!previousState) {
// This is the EMPTY state
return {
text: '',
replacePrevCharCnt: 0,
replaceNextCharCnt: 0,
positionDelta: 0
};
}
if (_debugComposition) {
console.log('------------------------deduceAndroidCompositionInput');
console.log(`PREVIOUS STATE: ${previousState.toString()}`);
console.log(`CURRENT STATE: ${currentState.toString()}`);
}
if (previousState.value === currentState.value) {
return {
text: '',
replacePrevCharCnt: 0,
replaceNextCharCnt: 0,
positionDelta: currentState.selectionEnd - previousState.selectionEnd
};
}
const prefixLength = Math.min(strings.commonPrefixLength(previousState.value, currentState.value), previousState.selectionEnd);
const suffixLength = Math.min(strings.commonSuffixLength(previousState.value, currentState.value), previousState.value.length - previousState.selectionEnd);
const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength);
const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength);
const previousSelectionStart = previousState.selectionStart - prefixLength;
const previousSelectionEnd = previousState.selectionEnd - prefixLength;
const currentSelectionStart = currentState.selectionStart - prefixLength;
const currentSelectionEnd = currentState.selectionEnd - prefixLength;
if (_debugComposition) {
console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`);
console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`);
}
return {
text: currentValue,
replacePrevCharCnt: previousSelectionEnd,
replaceNextCharCnt: previousValue.length - previousSelectionEnd,
positionDelta: currentSelectionEnd - currentValue.length
};
}
}
export class PagedScreenReaderStrategy {
static _getPageOfLine(lineNumber, linesPerPage) {
return Math.floor((lineNumber - 1) / linesPerPage);
}
static _getRangeForPage(page, linesPerPage) {
const offset = page * linesPerPage;
const startLineNumber = offset + 1;
const endLineNumber = offset + linesPerPage;
return new Range(startLineNumber, 1, endLineNumber + 1, 1);
}
static fromEditorSelection(model, selection, linesPerPage, trimLongText) {
// Chromium handles very poorly text even of a few thousand chars
// Cut text to avoid stalling the entire UI
const LIMIT_CHARS = 500;
const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber, linesPerPage);
const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage, linesPerPage);
const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber, linesPerPage);
const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage, linesPerPage);
let pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn));
if (trimLongText && model.getValueLengthInRange(pretextRange, 1 /* EndOfLinePreference.LF */) > LIMIT_CHARS) {
const pretextStart = model.modifyPosition(pretextRange.getEndPosition(), -LIMIT_CHARS);
pretextRange = Range.fromPositions(pretextStart, pretextRange.getEndPosition());
}
const pretext = model.getValueInRange(pretextRange, 1 /* EndOfLinePreference.LF */);
const lastLine = model.getLineCount();
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
let posttextRange = selectionEndPageRange.intersectRanges(new Range(selection.endLineNumber, selection.endColumn, lastLine, lastLineMaxColumn));
if (trimLongText && model.getValueLengthInRange(posttextRange, 1 /* EndOfLinePreference.LF */) > LIMIT_CHARS) {
const posttextEnd = model.modifyPosition(posttextRange.getStartPosition(), LIMIT_CHARS);
posttextRange = Range.fromPositions(posttextRange.getStartPosition(), posttextEnd);
}
const posttext = model.getValueInRange(posttextRange, 1 /* EndOfLinePreference.LF */);
let text;
if (selectionStartPage === selectionEndPage || selectionStartPage + 1 === selectionEndPage) {
// take full selection
text = model.getValueInRange(selection, 1 /* EndOfLinePreference.LF */);
}
else {
const selectionRange1 = selectionStartPageRange.intersectRanges(selection);
const selectionRange2 = selectionEndPageRange.intersectRanges(selection);
text = (model.getValueInRange(selectionRange1, 1 /* EndOfLinePreference.LF */)
+ String.fromCharCode(8230)
+ model.getValueInRange(selectionRange2, 1 /* EndOfLinePreference.LF */));
}
if (trimLongText && text.length > 2 * LIMIT_CHARS) {
text = text.substring(0, LIMIT_CHARS) + String.fromCharCode(8230) + text.substring(text.length - LIMIT_CHARS, text.length);
}
return new TextAreaState(pretext + text + posttext, pretext.length, pretext.length + text.length, selection, pretextRange.endLineNumber - pretextRange.startLineNumber);
}
}
@@ -0,0 +1 @@
export {}
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DataTransfers } from '../../base/browser/dnd.js';
import { createFileDataTransferItem, createStringDataTransferItem, UriList, VSDataTransfer } from '../../base/common/dataTransfer.js';
import { Mimes } from '../../base/common/mime.js';
import { URI } from '../../base/common/uri.js';
import { CodeDataTransfers } from '../../platform/dnd/browser/dnd.js';
export function toVSDataTransfer(dataTransfer) {
const vsDataTransfer = new VSDataTransfer();
for (const item of dataTransfer.items) {
const type = item.type;
if (item.kind === 'string') {
const asStringValue = new Promise(resolve => item.getAsString(resolve));
vsDataTransfer.append(type, createStringDataTransferItem(asStringValue));
}
else if (item.kind === 'file') {
const file = item.getAsFile();
if (file) {
vsDataTransfer.append(type, createFileDataTransferItemFromFile(file));
}
}
}
return vsDataTransfer;
}
function createFileDataTransferItemFromFile(file) {
const uri = file.path ? URI.parse(file.path) : undefined;
return createFileDataTransferItem(file.name, uri, async () => {
return new Uint8Array(await file.arrayBuffer());
});
}
const INTERNAL_DND_MIME_TYPES = Object.freeze([
CodeDataTransfers.EDITORS,
CodeDataTransfers.FILES,
DataTransfers.RESOURCES,
DataTransfers.INTERNAL_URI_LIST,
]);
export function toExternalVSDataTransfer(sourceDataTransfer, overwriteUriList = false) {
const vsDataTransfer = toVSDataTransfer(sourceDataTransfer);
// Try to expose the internal uri-list type as the standard type
const uriList = vsDataTransfer.get(DataTransfers.INTERNAL_URI_LIST);
if (uriList) {
vsDataTransfer.replace(Mimes.uriList, uriList);
}
else {
if (overwriteUriList || !vsDataTransfer.has(Mimes.uriList)) {
// Otherwise, fallback to adding dragged resources to the uri list
const editorData = [];
for (const item of sourceDataTransfer.items) {
const file = item.getAsFile();
if (file) {
const path = file.path;
try {
if (path) {
editorData.push(URI.file(path).toString());
}
else {
editorData.push(URI.parse(file.name, true).toString());
}
}
catch {
// Parsing failed. Leave out from list
}
}
}
if (editorData.length) {
vsDataTransfer.replace(Mimes.uriList, createStringDataTransferItem(UriList.create(editorData)));
}
}
}
for (const internal of INTERNAL_DND_MIME_TYPES) {
vsDataTransfer.delete(internal);
}
return vsDataTransfer;
}
@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as editorCommon from '../common/editorCommon.js';
/**
*@internal
*/
export function isCodeEditor(thing) {
if (thing && typeof thing.getEditorType === 'function') {
return thing.getEditorType() === editorCommon.EditorType.ICodeEditor;
}
else {
return false;
}
}
/**
*@internal
*/
export function isDiffEditor(thing) {
if (thing && typeof thing.getEditorType === 'function') {
return thing.getEditorType() === editorCommon.EditorType.IDiffEditor;
}
else {
return false;
}
}
/**
*@internal
*/
export function isCompositeEditor(thing) {
return !!thing
&& typeof thing === 'object'
&& typeof thing.onDidChangeActiveEditor === 'function';
}
/**
*@internal
*/
export function getCodeEditor(thing) {
if (isCodeEditor(thing)) {
return thing;
}
if (isDiffEditor(thing)) {
return thing.getModifiedEditor();
}
if (isCompositeEditor(thing) && isCodeEditor(thing.activeCodeEditor)) {
return thing.activeCodeEditor;
}
return null;
}
@@ -0,0 +1,282 @@
/*---------------------------------------------------------------------------------------------
* 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 { GlobalPointerMoveMonitor } from '../../base/browser/globalPointerMoveMonitor.js';
import { StandardMouseEvent } from '../../base/browser/mouseEvent.js';
import { RunOnceScheduler } from '../../base/common/async.js';
import { Disposable, DisposableStore } from '../../base/common/lifecycle.js';
import { asCssVariable } from '../../platform/theme/common/colorRegistry.js';
/**
* Coordinates relative to the whole document (e.g. mouse event's pageX and pageY)
*/
export class PageCoordinates {
constructor(x, y) {
this.x = x;
this.y = y;
this._pageCoordinatesBrand = undefined;
}
toClientCoordinates(targetWindow) {
return new ClientCoordinates(this.x - targetWindow.scrollX, this.y - targetWindow.scrollY);
}
}
/**
* Coordinates within the application's client area (i.e. origin is document's scroll position).
*
* For example, clicking in the top-left corner of the client area will
* always result in a mouse event with a client.x value of 0, regardless
* of whether the page is scrolled horizontally.
*/
export class ClientCoordinates {
constructor(clientX, clientY) {
this.clientX = clientX;
this.clientY = clientY;
this._clientCoordinatesBrand = undefined;
}
toPageCoordinates(targetWindow) {
return new PageCoordinates(this.clientX + targetWindow.scrollX, this.clientY + targetWindow.scrollY);
}
}
/**
* The position of the editor in the page.
*/
export class EditorPagePosition {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this._editorPagePositionBrand = undefined;
}
}
/**
* Coordinates relative to the the (top;left) of the editor that can be used safely with other internal editor metrics.
* **NOTE**: This position is obtained by taking page coordinates and transforming them relative to the
* editor's (top;left) position in a way in which scale transformations are taken into account.
* **NOTE**: These coordinates could be negative if the mouse position is outside the editor.
*/
export class CoordinatesRelativeToEditor {
constructor(x, y) {
this.x = x;
this.y = y;
this._positionRelativeToEditorBrand = undefined;
}
}
export function createEditorPagePosition(editorViewDomNode) {
const editorPos = dom.getDomNodePagePosition(editorViewDomNode);
return new EditorPagePosition(editorPos.left, editorPos.top, editorPos.width, editorPos.height);
}
export function createCoordinatesRelativeToEditor(editorViewDomNode, editorPagePosition, pos) {
// The editor's page position is read from the DOM using getBoundingClientRect().
//
// getBoundingClientRect() returns the actual dimensions, while offsetWidth and offsetHeight
// reflect the unscaled size. We can use this difference to detect a transform:scale()
// and we will apply the transformation in inverse to get mouse coordinates that make sense inside the editor.
//
// This could be expanded to cover rotation as well maybe by walking the DOM up from `editorViewDomNode`
// and computing the effective transformation matrix using getComputedStyle(element).transform.
//
const scaleX = editorPagePosition.width / editorViewDomNode.offsetWidth;
const scaleY = editorPagePosition.height / editorViewDomNode.offsetHeight;
// Adjust mouse offsets if editor appears to be scaled via transforms
const relativeX = (pos.x - editorPagePosition.x) / scaleX;
const relativeY = (pos.y - editorPagePosition.y) / scaleY;
return new CoordinatesRelativeToEditor(relativeX, relativeY);
}
export class EditorMouseEvent extends StandardMouseEvent {
constructor(e, isFromPointerCapture, editorViewDomNode) {
super(dom.getWindow(editorViewDomNode), e);
this._editorMouseEventBrand = undefined;
this.isFromPointerCapture = isFromPointerCapture;
this.pos = new PageCoordinates(this.posx, this.posy);
this.editorPos = createEditorPagePosition(editorViewDomNode);
this.relativePos = createCoordinatesRelativeToEditor(editorViewDomNode, this.editorPos, this.pos);
}
}
export class EditorMouseEventFactory {
constructor(editorViewDomNode) {
this._editorViewDomNode = editorViewDomNode;
}
_create(e) {
return new EditorMouseEvent(e, false, this._editorViewDomNode);
}
onContextMenu(target, callback) {
return dom.addDisposableListener(target, 'contextmenu', (e) => {
callback(this._create(e));
});
}
onMouseUp(target, callback) {
return dom.addDisposableListener(target, 'mouseup', (e) => {
callback(this._create(e));
});
}
onMouseDown(target, callback) {
return dom.addDisposableListener(target, dom.EventType.MOUSE_DOWN, (e) => {
callback(this._create(e));
});
}
onPointerDown(target, callback) {
return dom.addDisposableListener(target, dom.EventType.POINTER_DOWN, (e) => {
callback(this._create(e), e.pointerId);
});
}
onMouseLeave(target, callback) {
return dom.addDisposableListener(target, dom.EventType.MOUSE_LEAVE, (e) => {
callback(this._create(e));
});
}
onMouseMove(target, callback) {
return dom.addDisposableListener(target, 'mousemove', (e) => callback(this._create(e)));
}
}
export class EditorPointerEventFactory {
constructor(editorViewDomNode) {
this._editorViewDomNode = editorViewDomNode;
}
_create(e) {
return new EditorMouseEvent(e, false, this._editorViewDomNode);
}
onPointerUp(target, callback) {
return dom.addDisposableListener(target, 'pointerup', (e) => {
callback(this._create(e));
});
}
onPointerDown(target, callback) {
return dom.addDisposableListener(target, dom.EventType.POINTER_DOWN, (e) => {
callback(this._create(e), e.pointerId);
});
}
onPointerLeave(target, callback) {
return dom.addDisposableListener(target, dom.EventType.POINTER_LEAVE, (e) => {
callback(this._create(e));
});
}
onPointerMove(target, callback) {
return dom.addDisposableListener(target, 'pointermove', (e) => callback(this._create(e)));
}
}
export class GlobalEditorPointerMoveMonitor extends Disposable {
constructor(editorViewDomNode) {
super();
this._editorViewDomNode = editorViewDomNode;
this._globalPointerMoveMonitor = this._register(new GlobalPointerMoveMonitor());
this._keydownListener = null;
}
startMonitoring(initialElement, pointerId, initialButtons, pointerMoveCallback, onStopCallback) {
// Add a <<capture>> keydown event listener that will cancel the monitoring
// if something other than a modifier key is pressed
this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => {
const chord = e.toKeyCodeChord();
if (chord.isModifierKey()) {
// Allow modifier keys
return;
}
this._globalPointerMoveMonitor.stopMonitoring(true, e.browserEvent);
}, true);
this._globalPointerMoveMonitor.startMonitoring(initialElement, pointerId, initialButtons, (e) => {
pointerMoveCallback(new EditorMouseEvent(e, true, this._editorViewDomNode));
}, (e) => {
this._keydownListener.dispose();
onStopCallback(e);
});
}
stopMonitoring() {
this._globalPointerMoveMonitor.stopMonitoring(true);
}
}
/**
* A helper to create dynamic css rules, bound to a class name.
* Rules are reused.
* Reference counting and delayed garbage collection ensure that no rules leak.
*/
export class DynamicCssRules {
static { this._idPool = 0; }
constructor(_editor) {
this._editor = _editor;
this._instanceId = ++DynamicCssRules._idPool;
this._counter = 0;
this._rules = new Map();
// We delay garbage collection so that hanging rules can be reused.
this._garbageCollectionScheduler = new RunOnceScheduler(() => this.garbageCollect(), 1000);
}
createClassNameRef(options) {
const rule = this.getOrCreateRule(options);
rule.increaseRefCount();
return {
className: rule.className,
dispose: () => {
rule.decreaseRefCount();
this._garbageCollectionScheduler.schedule();
}
};
}
getOrCreateRule(properties) {
const key = this.computeUniqueKey(properties);
let existingRule = this._rules.get(key);
if (!existingRule) {
const counter = this._counter++;
existingRule = new RefCountedCssRule(key, `dyn-rule-${this._instanceId}-${counter}`, dom.isInShadowDOM(this._editor.getContainerDomNode())
? this._editor.getContainerDomNode()
: undefined, properties);
this._rules.set(key, existingRule);
}
return existingRule;
}
computeUniqueKey(properties) {
return JSON.stringify(properties);
}
garbageCollect() {
for (const rule of this._rules.values()) {
if (!rule.hasReferences()) {
this._rules.delete(rule.key);
rule.dispose();
}
}
}
}
class RefCountedCssRule {
constructor(key, className, _containerElement, properties) {
this.key = key;
this.className = className;
this.properties = properties;
this._referenceCount = 0;
this._styleElementDisposables = new DisposableStore();
this._styleElement = dom.createStyleSheet(_containerElement, undefined, this._styleElementDisposables);
this._styleElement.textContent = this.getCssText(this.className, this.properties);
}
getCssText(className, properties) {
let str = `.${className} {`;
for (const prop in properties) {
const value = properties[prop];
let cssValue;
if (typeof value === 'object') {
cssValue = asCssVariable(value.id);
}
else {
cssValue = value;
}
const cssPropName = camelToDashes(prop);
str += `\n\t${cssPropName}: ${cssValue};`;
}
str += `\n}`;
return str;
}
dispose() {
this._styleElementDisposables.dispose();
this._styleElement = undefined;
}
increaseRefCount() {
this._referenceCount++;
}
decreaseRefCount() {
this._referenceCount--;
}
hasReferences() {
return this._referenceCount > 0;
}
}
function camelToDashes(str) {
return str.replace(/(^[A-Z])/, ([first]) => first.toLowerCase())
.replace(/([A-Z])/g, ([letter]) => `-${letter.toLowerCase()}`);
}
@@ -0,0 +1,460 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from '../../nls.js';
import { URI } from '../../base/common/uri.js';
import { ICodeEditorService } from './services/codeEditorService.js';
import { Position } from '../common/core/position.js';
import { IModelService } from '../common/services/model.js';
import { ITextModelService } from '../common/services/resolverService.js';
import { MenuId, MenuRegistry, Action2 } from '../../platform/actions/common/actions.js';
import { CommandsRegistry } from '../../platform/commands/common/commands.js';
import { ContextKeyExpr, IContextKeyService } from '../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
import { KeybindingsRegistry } from '../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../platform/registry/common/platform.js';
import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
import { assertType } from '../../base/common/types.js';
import { ILogService } from '../../platform/log/common/log.js';
import { getActiveElement } from '../../base/browser/dom.js';
export class Command {
constructor(opts) {
this.id = opts.id;
this.precondition = opts.precondition;
this._kbOpts = opts.kbOpts;
this._menuOpts = opts.menuOpts;
this.metadata = opts.metadata;
}
register() {
if (Array.isArray(this._menuOpts)) {
this._menuOpts.forEach(this._registerMenuItem, this);
}
else if (this._menuOpts) {
this._registerMenuItem(this._menuOpts);
}
if (this._kbOpts) {
const kbOptsArr = Array.isArray(this._kbOpts) ? this._kbOpts : [this._kbOpts];
for (const kbOpts of kbOptsArr) {
let kbWhen = kbOpts.kbExpr;
if (this.precondition) {
if (kbWhen) {
kbWhen = ContextKeyExpr.and(kbWhen, this.precondition);
}
else {
kbWhen = this.precondition;
}
}
const desc = {
id: this.id,
weight: kbOpts.weight,
args: kbOpts.args,
when: kbWhen,
primary: kbOpts.primary,
secondary: kbOpts.secondary,
win: kbOpts.win,
linux: kbOpts.linux,
mac: kbOpts.mac,
};
KeybindingsRegistry.registerKeybindingRule(desc);
}
}
CommandsRegistry.registerCommand({
id: this.id,
handler: (accessor, args) => this.runCommand(accessor, args),
metadata: this.metadata
});
}
_registerMenuItem(item) {
MenuRegistry.appendMenuItem(item.menuId, {
group: item.group,
command: {
id: this.id,
title: item.title,
icon: item.icon,
precondition: this.precondition
},
when: item.when,
order: item.order
});
}
}
export class MultiCommand extends Command {
constructor() {
super(...arguments);
this._implementations = [];
}
/**
* A higher priority gets to be looked at first
*/
addImplementation(priority, name, implementation, when) {
this._implementations.push({ priority, name, implementation, when });
this._implementations.sort((a, b) => b.priority - a.priority);
return {
dispose: () => {
for (let i = 0; i < this._implementations.length; i++) {
if (this._implementations[i].implementation === implementation) {
this._implementations.splice(i, 1);
return;
}
}
}
};
}
runCommand(accessor, args) {
const logService = accessor.get(ILogService);
const contextKeyService = accessor.get(IContextKeyService);
logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`);
for (const impl of this._implementations) {
if (impl.when) {
const context = contextKeyService.getContext(getActiveElement());
const value = impl.when.evaluate(context);
if (!value) {
continue;
}
}
const result = impl.implementation(accessor, args);
if (result) {
logService.trace(`Command '${this.id}' was handled by '${impl.name}'.`);
if (typeof result === 'boolean') {
return;
}
return result;
}
}
logService.trace(`The Command '${this.id}' was not handled by any implementation.`);
}
}
//#endregion
/**
* A command that delegates to another command's implementation.
*
* This lets different commands be registered but share the same implementation
*/
export class ProxyCommand extends Command {
constructor(command, opts) {
super(opts);
this.command = command;
}
runCommand(accessor, args) {
return this.command.runCommand(accessor, args);
}
}
export class EditorCommand extends Command {
/**
* Create a command class that is bound to a certain editor contribution.
*/
static bindToContribution(controllerGetter) {
return class EditorControllerCommandImpl extends EditorCommand {
constructor(opts) {
super(opts);
this._callback = opts.handler;
}
runEditorCommand(accessor, editor, args) {
const controller = controllerGetter(editor);
if (controller) {
this._callback(controller, args);
}
}
};
}
static runEditorCommand(accessor, args, precondition, runner) {
const codeEditorService = accessor.get(ICodeEditorService);
// Find the editor with text focus or active
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
if (!editor) {
// well, at least we tried...
return;
}
return editor.invokeWithinContext((editorAccessor) => {
const kbService = editorAccessor.get(IContextKeyService);
if (!kbService.contextMatchesRules(precondition ?? undefined)) {
// precondition does not hold
return;
}
return runner(editorAccessor, editor, args);
});
}
runCommand(accessor, args) {
return EditorCommand.runEditorCommand(accessor, args, this.precondition, (accessor, editor, args) => this.runEditorCommand(accessor, editor, args));
}
}
export class EditorAction extends EditorCommand {
static convertOptions(opts) {
let menuOpts;
if (Array.isArray(opts.menuOpts)) {
menuOpts = opts.menuOpts;
}
else if (opts.menuOpts) {
menuOpts = [opts.menuOpts];
}
else {
menuOpts = [];
}
function withDefaults(item) {
if (!item.menuId) {
item.menuId = MenuId.EditorContext;
}
if (!item.title) {
item.title = opts.label;
}
item.when = ContextKeyExpr.and(opts.precondition, item.when);
return item;
}
if (Array.isArray(opts.contextMenuOpts)) {
menuOpts.push(...opts.contextMenuOpts.map(withDefaults));
}
else if (opts.contextMenuOpts) {
menuOpts.push(withDefaults(opts.contextMenuOpts));
}
opts.menuOpts = menuOpts;
return opts;
}
constructor(opts) {
super(EditorAction.convertOptions(opts));
this.label = opts.label;
this.alias = opts.alias;
}
runEditorCommand(accessor, editor, args) {
this.reportTelemetry(accessor, editor);
return this.run(accessor, editor, args || {});
}
reportTelemetry(accessor, editor) {
accessor.get(ITelemetryService).publicLog2('editorActionInvoked', { name: this.label, id: this.id });
}
}
export class MultiEditorAction extends EditorAction {
constructor() {
super(...arguments);
this._implementations = [];
}
/**
* A higher priority gets to be looked at first
*/
addImplementation(priority, implementation) {
this._implementations.push([priority, implementation]);
this._implementations.sort((a, b) => b[0] - a[0]);
return {
dispose: () => {
for (let i = 0; i < this._implementations.length; i++) {
if (this._implementations[i][1] === implementation) {
this._implementations.splice(i, 1);
return;
}
}
}
};
}
run(accessor, editor, args) {
for (const impl of this._implementations) {
const result = impl[1](accessor, editor, args);
if (result) {
if (typeof result === 'boolean') {
return;
}
return result;
}
}
}
}
//#endregion EditorAction
//#region EditorAction2
export class EditorAction2 extends Action2 {
run(accessor, ...args) {
// Find the editor with text focus or active
const codeEditorService = accessor.get(ICodeEditorService);
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
if (!editor) {
// well, at least we tried...
return;
}
// precondition does hold
return editor.invokeWithinContext((editorAccessor) => {
const kbService = editorAccessor.get(IContextKeyService);
const logService = editorAccessor.get(ILogService);
const enabled = kbService.contextMatchesRules(this.desc.precondition ?? undefined);
if (!enabled) {
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
return;
}
return this.runEditorCommand(editorAccessor, editor, ...args);
});
}
}
//#endregion
// --- Registration of commands and actions
export function registerModelAndPositionCommand(id, handler) {
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
const instaService = accessor.get(IInstantiationService);
const [resource, position] = args;
assertType(URI.isUri(resource));
assertType(Position.isIPosition(position));
const model = accessor.get(IModelService).getModel(resource);
if (model) {
const editorPosition = Position.lift(position);
return instaService.invokeFunction(handler, model, editorPosition, ...args.slice(2));
}
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
return new Promise((resolve, reject) => {
try {
const result = instaService.invokeFunction(handler, reference.object.textEditorModel, Position.lift(position), args.slice(2));
resolve(result);
}
catch (err) {
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
});
}
export function registerEditorCommand(editorCommand) {
EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand);
return editorCommand;
}
export function registerEditorAction(ctor) {
const action = new ctor();
EditorContributionRegistry.INSTANCE.registerEditorAction(action);
return action;
}
export function registerMultiEditorAction(action) {
EditorContributionRegistry.INSTANCE.registerEditorAction(action);
return action;
}
export function registerInstantiatedEditorAction(editorAction) {
EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
}
/**
* Registers an editor contribution. Editor contributions have a lifecycle which is bound
* to a specific code editor instance.
*/
export function registerEditorContribution(id, ctor, instantiation) {
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor, instantiation);
}
export var EditorExtensionsRegistry;
(function (EditorExtensionsRegistry) {
function getEditorCommand(commandId) {
return EditorContributionRegistry.INSTANCE.getEditorCommand(commandId);
}
EditorExtensionsRegistry.getEditorCommand = getEditorCommand;
function getEditorActions() {
return EditorContributionRegistry.INSTANCE.getEditorActions();
}
EditorExtensionsRegistry.getEditorActions = getEditorActions;
function getEditorContributions() {
return EditorContributionRegistry.INSTANCE.getEditorContributions();
}
EditorExtensionsRegistry.getEditorContributions = getEditorContributions;
function getSomeEditorContributions(ids) {
return EditorContributionRegistry.INSTANCE.getEditorContributions().filter(c => ids.indexOf(c.id) >= 0);
}
EditorExtensionsRegistry.getSomeEditorContributions = getSomeEditorContributions;
function getDiffEditorContributions() {
return EditorContributionRegistry.INSTANCE.getDiffEditorContributions();
}
EditorExtensionsRegistry.getDiffEditorContributions = getDiffEditorContributions;
})(EditorExtensionsRegistry || (EditorExtensionsRegistry = {}));
// Editor extension points
const Extensions = {
EditorCommonContributions: 'editor.contributions'
};
class EditorContributionRegistry {
static { this.INSTANCE = new EditorContributionRegistry(); }
constructor() {
this.editorContributions = [];
this.diffEditorContributions = [];
this.editorActions = [];
this.editorCommands = Object.create(null);
}
registerEditorContribution(id, ctor, instantiation) {
this.editorContributions.push({ id, ctor: ctor, instantiation });
}
getEditorContributions() {
return this.editorContributions.slice(0);
}
getDiffEditorContributions() {
return this.diffEditorContributions.slice(0);
}
registerEditorAction(action) {
action.register();
this.editorActions.push(action);
}
getEditorActions() {
return this.editorActions;
}
registerEditorCommand(editorCommand) {
editorCommand.register();
this.editorCommands[editorCommand.id] = editorCommand;
}
getEditorCommand(commandId) {
return (this.editorCommands[commandId] || null);
}
}
Registry.add(Extensions.EditorCommonContributions, EditorContributionRegistry.INSTANCE);
function registerCommand(command) {
command.register();
return command;
}
export const UndoCommand = registerCommand(new MultiCommand({
id: 'undo',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
primary: 2048 /* KeyMod.CtrlCmd */ | 56 /* KeyCode.KeyZ */
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
registerCommand(new ProxyCommand(UndoCommand, { id: 'default:undo', precondition: undefined }));
export const RedoCommand = registerCommand(new MultiCommand({
id: 'redo',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
primary: 2048 /* KeyMod.CtrlCmd */ | 55 /* KeyCode.KeyY */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */],
mac: { primary: 2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */ }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
registerCommand(new ProxyCommand(RedoCommand, { id: 'default:redo', precondition: undefined }));
export const SelectAllCommand = registerCommand(new MultiCommand({
id: 'editor.action.selectAll',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
kbExpr: null,
primary: 2048 /* KeyMod.CtrlCmd */ | 31 /* KeyCode.KeyA */
},
menuOpts: [{
menuId: MenuId.MenubarSelectionMenu,
group: '1_basic',
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
}));
@@ -0,0 +1,235 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { equalsIfDefined, itemsEquals } from '../../base/common/equals.js';
import { Disposable, DisposableStore, toDisposable } from '../../base/common/lifecycle.js';
import { autorun, autorunOpts, autorunWithStoreHandleChanges, derived, derivedOpts, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js';
import { TransactionImpl } from '../../base/common/observableInternal/base.js';
import { derivedWithSetter } from '../../base/common/observableInternal/derived.js';
import { Selection } from '../common/core/selection.js';
/**
* Returns a facade for the code editor that provides observables for various states/events.
*/
export function observableCodeEditor(editor) {
return ObservableCodeEditor.get(editor);
}
export class ObservableCodeEditor extends Disposable {
static { this._map = new Map(); }
/**
* Make sure that editor is not disposed yet!
*/
static get(editor) {
let result = ObservableCodeEditor._map.get(editor);
if (!result) {
result = new ObservableCodeEditor(editor);
ObservableCodeEditor._map.set(editor, result);
const d = editor.onDidDispose(() => {
const item = ObservableCodeEditor._map.get(editor);
if (item) {
ObservableCodeEditor._map.delete(editor);
item.dispose();
d.dispose();
}
});
}
return result;
}
_beginUpdate() {
this._updateCounter++;
if (this._updateCounter === 1) {
this._currentTransaction = new TransactionImpl(() => {
/** @description Update editor state */
});
}
}
_endUpdate() {
this._updateCounter--;
if (this._updateCounter === 0) {
const t = this._currentTransaction;
this._currentTransaction = undefined;
t.finish();
}
}
constructor(editor) {
super();
this.editor = editor;
this._updateCounter = 0;
this._currentTransaction = undefined;
this._model = observableValue(this, this.editor.getModel());
this.model = this._model;
this.isReadonly = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(92 /* EditorOption.readOnly */));
this._versionId = observableValueOpts({ owner: this, lazy: true }, this.editor.getModel()?.getVersionId() ?? null);
this.versionId = this._versionId;
this._selections = observableValueOpts({ owner: this, equalsFn: equalsIfDefined(itemsEquals(Selection.selectionsEqual)), lazy: true }, this.editor.getSelections() ?? null);
this.selections = this._selections;
this.isFocused = observableFromEvent(this, e => {
const d1 = this.editor.onDidFocusEditorWidget(e);
const d2 = this.editor.onDidBlurEditorWidget(e);
return {
dispose() {
d1.dispose();
d2.dispose();
}
};
}, () => this.editor.hasWidgetFocus());
this.value = derivedWithSetter(this, reader => { this.versionId.read(reader); return this.model.read(reader)?.getValue() ?? ''; }, (value, tx) => {
const model = this.model.get();
if (model !== null) {
if (value !== model.getValue()) {
model.setValue(value);
}
}
});
this.valueIsEmpty = derived(this, reader => { this.versionId.read(reader); return this.editor.getModel()?.getValueLength() === 0; });
this.cursorSelection = derivedOpts({ owner: this, equalsFn: equalsIfDefined(Selection.selectionsEqual) }, reader => this.selections.read(reader)?.[0] ?? null);
this.onDidType = observableSignal(this);
this.scrollTop = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollTop());
this.scrollLeft = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollLeft());
this.layoutInfo = observableFromEvent(this.editor.onDidLayoutChange, () => this.editor.getLayoutInfo());
this.layoutInfoContentLeft = this.layoutInfo.map(l => l.contentLeft);
this.layoutInfoDecorationsLeft = this.layoutInfo.map(l => l.decorationsLeft);
this.contentWidth = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentWidth());
this._overlayWidgetCounter = 0;
this._register(this.editor.onBeginUpdate(() => this._beginUpdate()));
this._register(this.editor.onEndUpdate(() => this._endUpdate()));
this._register(this.editor.onDidChangeModel(() => {
this._beginUpdate();
try {
this._model.set(this.editor.getModel(), this._currentTransaction);
this._forceUpdate();
}
finally {
this._endUpdate();
}
}));
this._register(this.editor.onDidType((e) => {
this._beginUpdate();
try {
this._forceUpdate();
this.onDidType.trigger(this._currentTransaction, e);
}
finally {
this._endUpdate();
}
}));
this._register(this.editor.onDidChangeModelContent(e => {
this._beginUpdate();
try {
this._versionId.set(this.editor.getModel()?.getVersionId() ?? null, this._currentTransaction, e);
this._forceUpdate();
}
finally {
this._endUpdate();
}
}));
this._register(this.editor.onDidChangeCursorSelection(e => {
this._beginUpdate();
try {
this._selections.set(this.editor.getSelections(), this._currentTransaction, e);
this._forceUpdate();
}
finally {
this._endUpdate();
}
}));
}
forceUpdate(cb) {
this._beginUpdate();
try {
this._forceUpdate();
if (!cb) {
return undefined;
}
return cb(this._currentTransaction);
}
finally {
this._endUpdate();
}
}
_forceUpdate() {
this._beginUpdate();
try {
this._model.set(this.editor.getModel(), this._currentTransaction);
this._versionId.set(this.editor.getModel()?.getVersionId() ?? null, this._currentTransaction, undefined);
this._selections.set(this.editor.getSelections(), this._currentTransaction, undefined);
}
finally {
this._endUpdate();
}
}
getOption(id) {
return observableFromEvent(this, cb => this.editor.onDidChangeConfiguration(e => {
if (e.hasChanged(id)) {
cb(undefined);
}
}), () => this.editor.getOption(id));
}
setDecorations(decorations) {
const d = new DisposableStore();
const decorationsCollection = this.editor.createDecorationsCollection();
d.add(autorunOpts({ owner: this, debugName: () => `Apply decorations from ${decorations.debugName}` }, reader => {
const d = decorations.read(reader);
decorationsCollection.set(d);
}));
d.add({
dispose: () => {
decorationsCollection.clear();
}
});
return d;
}
createOverlayWidget(widget) {
const overlayWidgetId = 'observableOverlayWidget' + (this._overlayWidgetCounter++);
const w = {
getDomNode: () => widget.domNode,
getPosition: () => widget.position.get(),
getId: () => overlayWidgetId,
allowEditorOverflow: widget.allowEditorOverflow,
getMinContentWidthInPx: () => widget.minContentWidthInPx.get(),
};
this.editor.addOverlayWidget(w);
const d = autorun(reader => {
widget.position.read(reader);
widget.minContentWidthInPx.read(reader);
this.editor.layoutOverlayWidget(w);
});
return toDisposable(() => {
d.dispose();
this.editor.removeOverlayWidget(w);
});
}
}
export function reactToChange(observable, cb) {
return autorunWithStoreHandleChanges({
createEmptyChangeSummary: () => ({ deltas: [], didChange: false }),
handleChange: (context, changeSummary) => {
if (context.didChange(observable)) {
const e = context.change;
if (e !== undefined) {
changeSummary.deltas.push(e);
}
changeSummary.didChange = true;
}
return true;
},
}, (reader, changeSummary) => {
const value = observable.read(reader);
if (changeSummary.didChange) {
cb(value, changeSummary.deltas);
}
});
}
export function reactToChangeWithStore(observable, cb) {
const store = new DisposableStore();
const disposable = reactToChange(observable, (value, deltas) => {
store.clear();
cb(value, deltas, store);
});
return {
dispose() {
disposable.dispose();
store.dispose();
}
};
}
@@ -0,0 +1,127 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { Emitter } from '../../../base/common/event.js';
import { Disposable, toDisposable } from '../../../base/common/lifecycle.js';
import { LinkedList } from '../../../base/common/linkedList.js';
import { IThemeService } from '../../../platform/theme/common/themeService.js';
let AbstractCodeEditorService = class AbstractCodeEditorService extends Disposable {
constructor(_themeService) {
super();
this._themeService = _themeService;
this._onWillCreateCodeEditor = this._register(new Emitter());
this._onCodeEditorAdd = this._register(new Emitter());
this.onCodeEditorAdd = this._onCodeEditorAdd.event;
this._onCodeEditorRemove = this._register(new Emitter());
this.onCodeEditorRemove = this._onCodeEditorRemove.event;
this._onWillCreateDiffEditor = this._register(new Emitter());
this._onDiffEditorAdd = this._register(new Emitter());
this.onDiffEditorAdd = this._onDiffEditorAdd.event;
this._onDiffEditorRemove = this._register(new Emitter());
this.onDiffEditorRemove = this._onDiffEditorRemove.event;
this._decorationOptionProviders = new Map();
this._codeEditorOpenHandlers = new LinkedList();
this._modelProperties = new Map();
this._codeEditors = Object.create(null);
this._diffEditors = Object.create(null);
this._globalStyleSheet = null;
}
willCreateCodeEditor() {
this._onWillCreateCodeEditor.fire();
}
addCodeEditor(editor) {
this._codeEditors[editor.getId()] = editor;
this._onCodeEditorAdd.fire(editor);
}
removeCodeEditor(editor) {
if (delete this._codeEditors[editor.getId()]) {
this._onCodeEditorRemove.fire(editor);
}
}
listCodeEditors() {
return Object.keys(this._codeEditors).map(id => this._codeEditors[id]);
}
willCreateDiffEditor() {
this._onWillCreateDiffEditor.fire();
}
addDiffEditor(editor) {
this._diffEditors[editor.getId()] = editor;
this._onDiffEditorAdd.fire(editor);
}
listDiffEditors() {
return Object.keys(this._diffEditors).map(id => this._diffEditors[id]);
}
getFocusedCodeEditor() {
let editorWithWidgetFocus = null;
const editors = this.listCodeEditors();
for (const editor of editors) {
if (editor.hasTextFocus()) {
// bingo!
return editor;
}
if (editor.hasWidgetFocus()) {
editorWithWidgetFocus = editor;
}
}
return editorWithWidgetFocus;
}
removeDecorationType(key) {
const provider = this._decorationOptionProviders.get(key);
if (provider) {
provider.refCount--;
if (provider.refCount <= 0) {
this._decorationOptionProviders.delete(key);
provider.dispose();
this.listCodeEditors().forEach((ed) => ed.removeDecorationsByType(key));
}
}
}
setModelProperty(resource, key, value) {
const key1 = resource.toString();
let dest;
if (this._modelProperties.has(key1)) {
dest = this._modelProperties.get(key1);
}
else {
dest = new Map();
this._modelProperties.set(key1, dest);
}
dest.set(key, value);
}
getModelProperty(resource, key) {
const key1 = resource.toString();
if (this._modelProperties.has(key1)) {
const innerMap = this._modelProperties.get(key1);
return innerMap.get(key);
}
return undefined;
}
async openCodeEditor(input, source, sideBySide) {
for (const handler of this._codeEditorOpenHandlers) {
const candidate = await handler(input, source, sideBySide);
if (candidate !== null) {
return candidate;
}
}
return null;
}
registerCodeEditorOpenHandler(handler) {
const rm = this._codeEditorOpenHandlers.unshift(handler);
return toDisposable(rm);
}
};
AbstractCodeEditorService = __decorate([
__param(0, IThemeService)
], AbstractCodeEditorService);
export { AbstractCodeEditorService };
export class GlobalStyleSheet {
constructor(styleSheet) {
this._styleSheet = styleSheet;
}
}
@@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
import { URI } from '../../../base/common/uri.js';
import { isObject } from '../../../base/common/types.js';
export const IBulkEditService = createDecorator('IWorkspaceEditService');
export class ResourceEdit {
constructor(metadata) {
this.metadata = metadata;
}
static convert(edit) {
return edit.edits.map(edit => {
if (ResourceTextEdit.is(edit)) {
return ResourceTextEdit.lift(edit);
}
if (ResourceFileEdit.is(edit)) {
return ResourceFileEdit.lift(edit);
}
throw new Error('Unsupported edit');
});
}
}
export class ResourceTextEdit extends ResourceEdit {
static is(candidate) {
if (candidate instanceof ResourceTextEdit) {
return true;
}
return isObject(candidate)
&& URI.isUri(candidate.resource)
&& isObject(candidate.textEdit);
}
static lift(edit) {
if (edit instanceof ResourceTextEdit) {
return edit;
}
else {
return new ResourceTextEdit(edit.resource, edit.textEdit, edit.versionId, edit.metadata);
}
}
constructor(resource, textEdit, versionId = undefined, metadata) {
super(metadata);
this.resource = resource;
this.textEdit = textEdit;
this.versionId = versionId;
}
}
export class ResourceFileEdit extends ResourceEdit {
static is(candidate) {
if (candidate instanceof ResourceFileEdit) {
return true;
}
else {
return isObject(candidate)
&& (Boolean(candidate.newResource) || Boolean(candidate.oldResource));
}
}
static lift(edit) {
if (edit instanceof ResourceFileEdit) {
return edit;
}
else {
return new ResourceFileEdit(edit.oldResource, edit.newResource, edit.options, edit.metadata);
}
}
constructor(oldResource, newResource, options = {}, metadata) {
super(metadata);
this.oldResource = oldResource;
this.newResource = newResource;
this.options = options;
}
}
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
export const ICodeEditorService = createDecorator('codeEditorService');
@@ -0,0 +1,459 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { IntervalTimer, timeout } from '../../../base/common/async.js';
import { Disposable, dispose, toDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { SimpleWorkerClient, logOnceWebWorkerWarning } from '../../../base/common/worker/simpleWorker.js';
import { DefaultWorkerFactory } from '../../../base/browser/defaultWorkerFactory.js';
import { Range } from '../../common/core/range.js';
import { ILanguageConfigurationService } from '../../common/languages/languageConfigurationRegistry.js';
import { EditorSimpleWorker } from '../../common/services/editorSimpleWorker.js';
import { IModelService } from '../../common/services/model.js';
import { ITextResourceConfigurationService } from '../../common/services/textResourceConfiguration.js';
import { isNonEmptyArray } from '../../../base/common/arrays.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { StopWatch } from '../../../base/common/stopwatch.js';
import { canceled } from '../../../base/common/errors.js';
import { ILanguageFeaturesService } from '../../common/services/languageFeatures.js';
import { MovedText } from '../../common/diff/linesDiffComputer.js';
import { DetailedLineRangeMapping, RangeMapping, LineRangeMapping } from '../../common/diff/rangeMapping.js';
import { LineRange } from '../../common/core/lineRange.js';
import { mainWindow } from '../../../base/browser/window.js';
import { WindowIntervalTimer } from '../../../base/browser/dom.js';
/**
* Stop syncing a model to the worker if it was not needed for 1 min.
*/
const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
/**
* Stop the worker if it was not needed for 5 min.
*/
const STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
function canSyncModel(modelService, resource) {
const model = modelService.getModel(resource);
if (!model) {
return false;
}
if (model.isTooLargeForSyncing()) {
return false;
}
return true;
}
let EditorWorkerService = class EditorWorkerService extends Disposable {
constructor(modelService, configurationService, logService, languageConfigurationService, languageFeaturesService) {
super();
this._modelService = modelService;
this._workerManager = this._register(new WorkerManager(this._modelService, languageConfigurationService));
this._logService = logService;
// register default link-provider and default completions-provider
this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, {
provideLinks: (model, token) => {
if (!canSyncModel(this._modelService, model.uri)) {
return Promise.resolve({ links: [] }); // File too large
}
return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => {
return links && { links };
});
}
}));
this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, languageConfigurationService)));
}
dispose() {
super.dispose();
}
canComputeUnicodeHighlights(uri) {
return canSyncModel(this._modelService, uri);
}
computedUnicodeHighlights(uri, options, range) {
return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
}
async computeDiff(original, modified, options, algorithm) {
const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm));
if (!result) {
return null;
}
// Convert from space efficient JSON data to rich objects.
const diff = {
identical: result.identical,
quitEarly: result.quitEarly,
changes: toLineRangeMappings(result.changes),
moves: result.moves.map(m => new MovedText(new LineRangeMapping(new LineRange(m[0], m[1]), new LineRange(m[2], m[3])), toLineRangeMappings(m[4])))
};
return diff;
function toLineRangeMappings(changes) {
return changes.map((c) => new DetailedLineRangeMapping(new LineRange(c[0], c[1]), new LineRange(c[2], c[3]), c[4]?.map((c) => new RangeMapping(new Range(c[0], c[1], c[2], c[3]), new Range(c[4], c[5], c[6], c[7])))));
}
}
computeMoreMinimalEdits(resource, edits, pretty = false) {
if (isNonEmptyArray(edits)) {
if (!canSyncModel(this._modelService, resource)) {
return Promise.resolve(edits); // File too large
}
const sw = StopWatch.create();
const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty));
result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
return Promise.race([result, timeout(1000).then(() => edits)]);
}
else {
return Promise.resolve(undefined);
}
}
canNavigateValueSet(resource) {
return (canSyncModel(this._modelService, resource));
}
navigateValueSet(resource, range, up) {
return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up));
}
canComputeWordRanges(resource) {
return canSyncModel(this._modelService, resource);
}
computeWordRanges(resource, range) {
return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range));
}
findSectionHeaders(uri, options) {
return this._workerManager.withWorker().then(client => client.findSectionHeaders(uri, options));
}
};
EditorWorkerService = __decorate([
__param(0, IModelService),
__param(1, ITextResourceConfigurationService),
__param(2, ILogService),
__param(3, ILanguageConfigurationService),
__param(4, ILanguageFeaturesService)
], EditorWorkerService);
export { EditorWorkerService };
class WordBasedCompletionItemProvider {
constructor(workerManager, configurationService, modelService, languageConfigurationService) {
this.languageConfigurationService = languageConfigurationService;
this._debugDisplayName = 'wordbasedCompletions';
this._workerManager = workerManager;
this._configurationService = configurationService;
this._modelService = modelService;
}
async provideCompletionItems(model, position) {
const config = this._configurationService.getValue(model.uri, position, 'editor');
if (config.wordBasedSuggestions === 'off') {
return undefined;
}
const models = [];
if (config.wordBasedSuggestions === 'currentDocument') {
// only current file and only if not too large
if (canSyncModel(this._modelService, model.uri)) {
models.push(model.uri);
}
}
else {
// either all files or files of same language
for (const candidate of this._modelService.getModels()) {
if (!canSyncModel(this._modelService, candidate.uri)) {
continue;
}
if (candidate === model) {
models.unshift(candidate.uri);
}
else if (config.wordBasedSuggestions === 'allDocuments' || candidate.getLanguageId() === model.getLanguageId()) {
models.push(candidate.uri);
}
}
}
if (models.length === 0) {
return undefined; // File too large, no other files
}
const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
const word = model.getWordAtPosition(position);
const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
const insert = replace.setEndPosition(position.lineNumber, position.column);
const client = await this._workerManager.withWorker();
const data = await client.textualSuggest(models, word?.word, wordDefRegExp);
if (!data) {
return undefined;
}
return {
duration: data.duration,
suggestions: data.words.map((word) => {
return {
kind: 18 /* languages.CompletionItemKind.Text */,
label: word,
insertText: word,
range: { insert, replace }
};
}),
};
}
}
class WorkerManager extends Disposable {
constructor(modelService, languageConfigurationService) {
super();
this.languageConfigurationService = languageConfigurationService;
this._modelService = modelService;
this._editorWorkerClient = null;
this._lastWorkerUsedTime = (new Date()).getTime();
const stopWorkerInterval = this._register(new WindowIntervalTimer());
stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2), mainWindow);
this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker()));
}
dispose() {
if (this._editorWorkerClient) {
this._editorWorkerClient.dispose();
this._editorWorkerClient = null;
}
super.dispose();
}
/**
* Check if the model service has no more models and stop the worker if that is the case.
*/
_checkStopEmptyWorker() {
if (!this._editorWorkerClient) {
return;
}
const models = this._modelService.getModels();
if (models.length === 0) {
// There are no more models => nothing possible for me to do
this._editorWorkerClient.dispose();
this._editorWorkerClient = null;
}
}
/**
* Check if the worker has been idle for a while and then stop it.
*/
_checkStopIdleWorker() {
if (!this._editorWorkerClient) {
return;
}
const timeSinceLastWorkerUsedTime = (new Date()).getTime() - this._lastWorkerUsedTime;
if (timeSinceLastWorkerUsedTime > STOP_WORKER_DELTA_TIME_MS) {
this._editorWorkerClient.dispose();
this._editorWorkerClient = null;
}
}
withWorker() {
this._lastWorkerUsedTime = (new Date()).getTime();
if (!this._editorWorkerClient) {
this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService', this.languageConfigurationService);
}
return Promise.resolve(this._editorWorkerClient);
}
}
class EditorModelManager extends Disposable {
constructor(proxy, modelService, keepIdleModels) {
super();
this._syncedModels = Object.create(null);
this._syncedModelsLastUsedTime = Object.create(null);
this._proxy = proxy;
this._modelService = modelService;
if (!keepIdleModels) {
const timer = new IntervalTimer();
timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2));
this._register(timer);
}
}
dispose() {
for (const modelUrl in this._syncedModels) {
dispose(this._syncedModels[modelUrl]);
}
this._syncedModels = Object.create(null);
this._syncedModelsLastUsedTime = Object.create(null);
super.dispose();
}
ensureSyncedResources(resources, forceLargeModels) {
for (const resource of resources) {
const resourceStr = resource.toString();
if (!this._syncedModels[resourceStr]) {
this._beginModelSync(resource, forceLargeModels);
}
if (this._syncedModels[resourceStr]) {
this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime();
}
}
}
_checkStopModelSync() {
const currentTime = (new Date()).getTime();
const toRemove = [];
for (const modelUrl in this._syncedModelsLastUsedTime) {
const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl];
if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) {
toRemove.push(modelUrl);
}
}
for (const e of toRemove) {
this._stopModelSync(e);
}
}
_beginModelSync(resource, forceLargeModels) {
const model = this._modelService.getModel(resource);
if (!model) {
return;
}
if (!forceLargeModels && model.isTooLargeForSyncing()) {
return;
}
const modelUrl = resource.toString();
this._proxy.acceptNewModel({
url: model.uri.toString(),
lines: model.getLinesContent(),
EOL: model.getEOL(),
versionId: model.getVersionId()
});
const toDispose = new DisposableStore();
toDispose.add(model.onDidChangeContent((e) => {
this._proxy.acceptModelChanged(modelUrl.toString(), e);
}));
toDispose.add(model.onWillDispose(() => {
this._stopModelSync(modelUrl);
}));
toDispose.add(toDisposable(() => {
this._proxy.acceptRemovedModel(modelUrl);
}));
this._syncedModels[modelUrl] = toDispose;
}
_stopModelSync(modelUrl) {
const toDispose = this._syncedModels[modelUrl];
delete this._syncedModels[modelUrl];
delete this._syncedModelsLastUsedTime[modelUrl];
dispose(toDispose);
}
}
class SynchronousWorkerClient {
constructor(instance) {
this._instance = instance;
this._proxyObj = Promise.resolve(this._instance);
}
dispose() {
this._instance.dispose();
}
getProxyObject() {
return this._proxyObj;
}
}
export class EditorWorkerHost {
constructor(workerClient) {
this._workerClient = workerClient;
}
// foreign host request
fhr(method, args) {
return this._workerClient.fhr(method, args);
}
}
export class EditorWorkerClient extends Disposable {
constructor(modelService, keepIdleModels, label, languageConfigurationService) {
super();
this.languageConfigurationService = languageConfigurationService;
this._disposed = false;
this._modelService = modelService;
this._keepIdleModels = keepIdleModels;
this._workerFactory = new DefaultWorkerFactory(label);
this._worker = null;
this._modelManager = null;
}
// foreign host request
fhr(method, args) {
throw new Error(`Not implemented!`);
}
_getOrCreateWorker() {
if (!this._worker) {
try {
this._worker = this._register(new SimpleWorkerClient(this._workerFactory, 'vs/editor/common/services/editorSimpleWorker', new EditorWorkerHost(this)));
}
catch (err) {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null));
}
}
return this._worker;
}
_getProxy() {
return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null));
return this._getOrCreateWorker().getProxyObject();
});
}
_getOrCreateModelManager(proxy) {
if (!this._modelManager) {
this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels));
}
return this._modelManager;
}
async _withSyncedResources(resources, forceLargeModels = false) {
if (this._disposed) {
return Promise.reject(canceled());
}
return this._getProxy().then((proxy) => {
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels);
return proxy;
});
}
computedUnicodeHighlights(uri, options, range) {
return this._withSyncedResources([uri]).then(proxy => {
return proxy.computeUnicodeHighlights(uri.toString(), options, range);
});
}
computeDiff(original, modified, options, algorithm) {
return this._withSyncedResources([original, modified], /* forceLargeModels */ true).then(proxy => {
return proxy.computeDiff(original.toString(), modified.toString(), options, algorithm);
});
}
computeMoreMinimalEdits(resource, edits, pretty) {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeMoreMinimalEdits(resource.toString(), edits, pretty);
});
}
computeLinks(resource) {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeLinks(resource.toString());
});
}
computeDefaultDocumentColors(resource) {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeDefaultDocumentColors(resource.toString());
});
}
async textualSuggest(resources, leadingWord, wordDefRegExp) {
const proxy = await this._withSyncedResources(resources);
const wordDef = wordDefRegExp.source;
const wordDefFlags = wordDefRegExp.flags;
return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags);
}
computeWordRanges(resource, range) {
return this._withSyncedResources([resource]).then(proxy => {
const model = this._modelService.getModel(resource);
if (!model) {
return Promise.resolve(null);
}
const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
const wordDef = wordDefRegExp.source;
const wordDefFlags = wordDefRegExp.flags;
return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags);
});
}
navigateValueSet(resource, range, up) {
return this._withSyncedResources([resource]).then(proxy => {
const model = this._modelService.getModel(resource);
if (!model) {
return null;
}
const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
const wordDef = wordDefRegExp.source;
const wordDefFlags = wordDefRegExp.flags;
return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
});
}
findSectionHeaders(uri, options) {
return this._withSyncedResources([uri]).then(proxy => {
return proxy.findSectionHeaders(uri.toString(), options);
});
}
dispose() {
super.dispose();
this._disposed = true;
}
}
@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .workbench-hover {
position: relative;
font-size: 13px;
line-height: 19px;
/* Must be higher than sash's z-index and terminal canvases */
z-index: 40;
overflow: hidden;
max-width: 700px;
background: var(--vscode-editorHoverWidget-background);
border: 1px solid var(--vscode-editorHoverWidget-border);
border-radius: 3px;
color: var(--vscode-editorHoverWidget-foreground);
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
}
.monaco-workbench .workbench-hover hr {
border-bottom: none;
}
.monaco-workbench .workbench-hover:not(.skip-fade-in) {
animation: fadein 100ms linear;
}
.monaco-workbench .workbench-hover.compact {
font-size: 12px;
}
.monaco-workbench .workbench-hover.compact .hover-contents {
padding: 2px 8px;
}
.monaco-workbench .workbench-hover-container.locked .workbench-hover {
outline: 1px solid var(--vscode-editorHoverWidget-border);
}
.monaco-workbench .workbench-hover-container.locked .workbench-hover:focus,
.monaco-workbench .workbench-hover-lock:focus {
outline: 1px solid var(--vscode-focusBorder);
}
.monaco-workbench .workbench-hover-container.locked .workbench-hover-lock:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.monaco-workbench .workbench-hover-pointer {
position: absolute;
/* Must be higher than workbench hover z-index */
z-index: 41;
pointer-events: none;
}
.monaco-workbench .workbench-hover-pointer:after {
content: '';
position: absolute;
width: 5px;
height: 5px;
background-color: var(--vscode-editorHoverWidget-background);
border-right: 1px solid var(--vscode-editorHoverWidget-border);
border-bottom: 1px solid var(--vscode-editorHoverWidget-border);
}
.monaco-workbench .locked .workbench-hover-pointer:after {
width: 4px;
height: 4px;
border-right-width: 2px;
border-bottom-width: 2px;
}
.monaco-workbench .workbench-hover-pointer.left { left: -3px; }
.monaco-workbench .workbench-hover-pointer.right { right: 3px; }
.monaco-workbench .workbench-hover-pointer.top { top: -3px; }
.monaco-workbench .workbench-hover-pointer.bottom { bottom: 3px; }
.monaco-workbench .workbench-hover-pointer.left:after {
transform: rotate(135deg);
}
.monaco-workbench .workbench-hover-pointer.right:after {
transform: rotate(315deg);
}
.monaco-workbench .workbench-hover-pointer.top:after {
transform: rotate(225deg);
}
.monaco-workbench .workbench-hover-pointer.bottom:after {
transform: rotate(45deg);
}
.monaco-workbench .workbench-hover a {
color: var(--vscode-textLink-foreground);
}
.monaco-workbench .workbench-hover a:focus {
outline: 1px solid;
outline-offset: -1px;
text-decoration: underline;
outline-color: var(--vscode-focusBorder);
}
.monaco-workbench .workbench-hover a:hover,
.monaco-workbench .workbench-hover a:active {
color: var(--vscode-textLink-activeForeground);
}
.monaco-workbench .workbench-hover code {
background: var(--vscode-textCodeBlock-background);
}
.monaco-workbench .workbench-hover .hover-row .actions {
background: var(--vscode-editorHoverWidget-statusBarBackground);
}
.monaco-workbench .workbench-hover.right-aligned {
/* The context view service wraps strangely when it's right up against the edge without this */
left: 1px;
}
.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions {
flex-direction: row-reverse;
}
.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions .action-container {
margin-right: 0;
margin-left: 16px;
}
@@ -0,0 +1,354 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { editorHoverBorder } from '../../../../platform/theme/common/colorRegistry.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { HoverWidget } from './hoverWidget.js';
import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement } from '../../../../base/browser/dom.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';
import { mainWindow } from '../../../../base/browser/window.js';
import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js';
import { ManagedHoverWidget } from './updatableHoverWidget.js';
import { TimeoutTimer } from '../../../../base/common/async.js';
let HoverService = class HoverService extends Disposable {
constructor(_instantiationService, contextMenuService, _keybindingService, _layoutService, _accessibilityService) {
super();
this._instantiationService = _instantiationService;
this._keybindingService = _keybindingService;
this._layoutService = _layoutService;
this._accessibilityService = _accessibilityService;
this._managedHovers = new Map();
contextMenuService.onDidShowContextMenu(() => this.hideHover());
this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService));
}
showHover(options, focus, skipLastFocusedUpdate) {
if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) {
return undefined;
}
if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) {
return undefined;
}
this._currentHoverOptions = options;
this._lastHoverOptions = options;
const trapFocus = options.trapFocus || this._accessibilityService.isScreenReaderOptimized();
const activeElement = getActiveElement();
// HACK, remove this check when #189076 is fixed
if (!skipLastFocusedUpdate) {
if (trapFocus && activeElement) {
if (!activeElement.classList.contains('monaco-hover')) {
this._lastFocusedElementBeforeOpen = activeElement;
}
}
else {
this._lastFocusedElementBeforeOpen = undefined;
}
}
const hoverDisposables = new DisposableStore();
const hover = this._instantiationService.createInstance(HoverWidget, options);
if (options.persistence?.sticky) {
hover.isLocked = true;
}
hover.onDispose(() => {
const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode);
if (hoverWasFocused) {
// Required to handle cases such as closing the hover with the escape key
this._lastFocusedElementBeforeOpen?.focus();
}
// Only clear the current options if it's the current hover, the current options help
// reduce flickering when the same hover is shown multiple times
if (this._currentHoverOptions === options) {
this._currentHoverOptions = undefined;
}
hoverDisposables.dispose();
}, undefined, hoverDisposables);
// Set the container explicitly to enable aux window support
if (!options.container) {
const targetElement = isHTMLElement(options.target) ? options.target : options.target.targetElements[0];
options.container = this._layoutService.getContainer(getWindow(targetElement));
}
this._contextViewHandler.showContextView(new HoverContextViewDelegate(hover, focus), options.container);
hover.onRequestLayout(() => this._contextViewHandler.layout(), undefined, hoverDisposables);
if (options.persistence?.sticky) {
hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => {
if (!isAncestor(e.target, hover.domNode)) {
this.doHideHover();
}
}));
}
else {
if ('targetElements' in options.target) {
for (const element of options.target.targetElements) {
hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover()));
}
}
else {
hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover()));
}
const focusedElement = getActiveElement();
if (focusedElement) {
const focusedElementDocument = getWindow(focusedElement).document;
hoverDisposables.add(addDisposableListener(focusedElement, EventType.KEY_DOWN, e => this._keyDown(e, hover, !!options.persistence?.hideOnKeyDown)));
hoverDisposables.add(addDisposableListener(focusedElementDocument, EventType.KEY_DOWN, e => this._keyDown(e, hover, !!options.persistence?.hideOnKeyDown)));
hoverDisposables.add(addDisposableListener(focusedElement, EventType.KEY_UP, e => this._keyUp(e, hover)));
hoverDisposables.add(addDisposableListener(focusedElementDocument, EventType.KEY_UP, e => this._keyUp(e, hover)));
}
}
if ('IntersectionObserver' in mainWindow) {
const observer = new IntersectionObserver(e => this._intersectionChange(e, hover), { threshold: 0 });
const firstTargetElement = 'targetElements' in options.target ? options.target.targetElements[0] : options.target;
observer.observe(firstTargetElement);
hoverDisposables.add(toDisposable(() => observer.disconnect()));
}
this._currentHover = hover;
return hover;
}
hideHover() {
if (this._currentHover?.isLocked || !this._currentHoverOptions) {
return;
}
this.doHideHover();
}
doHideHover() {
this._currentHover = undefined;
this._currentHoverOptions = undefined;
this._contextViewHandler.hideContextView();
}
_intersectionChange(entries, hover) {
const entry = entries[entries.length - 1];
if (!entry.isIntersecting) {
hover.dispose();
}
}
showAndFocusLastHover() {
if (!this._lastHoverOptions) {
return;
}
this.showHover(this._lastHoverOptions, true, true);
}
_keyDown(e, hover, hideOnKeyDown) {
if (e.key === 'Alt') {
hover.isLocked = true;
return;
}
const event = new StandardKeyboardEvent(e);
const keybinding = this._keybindingService.resolveKeyboardEvent(event);
if (keybinding.getSingleModifierDispatchChords().some(value => !!value) || this._keybindingService.softDispatch(event, event.target).kind !== 0 /* ResultKind.NoMatchingKb */) {
return;
}
if (hideOnKeyDown && (!this._currentHoverOptions?.trapFocus || e.key !== 'Tab')) {
this.hideHover();
this._lastFocusedElementBeforeOpen?.focus();
}
}
_keyUp(e, hover) {
if (e.key === 'Alt') {
hover.isLocked = false;
// Hide if alt is released while the mouse is not over hover/target
if (!hover.isMouseIn) {
this.hideHover();
this._lastFocusedElementBeforeOpen?.focus();
}
}
}
// TODO: Investigate performance of this function. There seems to be a lot of content created
// and thrown away on start up
setupManagedHover(hoverDelegate, targetElement, content, options) {
targetElement.setAttribute('custom-hover', 'true');
if (targetElement.title !== '') {
console.warn('HTML element already has a title attribute, which will conflict with the custom hover. Please remove the title attribute.');
console.trace('Stack trace:', targetElement.title);
targetElement.title = '';
}
let hoverPreparation;
let hoverWidget;
const hideHover = (disposeWidget, disposePreparation) => {
const hadHover = hoverWidget !== undefined;
if (disposeWidget) {
hoverWidget?.dispose();
hoverWidget = undefined;
}
if (disposePreparation) {
hoverPreparation?.dispose();
hoverPreparation = undefined;
}
if (hadHover) {
hoverDelegate.onDidHideHover?.();
hoverWidget = undefined;
}
};
const triggerShowHover = (delay, focus, target, trapFocus) => {
return new TimeoutTimer(async () => {
if (!hoverWidget || hoverWidget.isDisposed) {
hoverWidget = new ManagedHoverWidget(hoverDelegate, target || targetElement, delay > 0);
await hoverWidget.update(typeof content === 'function' ? content() : content, focus, { ...options, trapFocus });
}
}, delay);
};
let isMouseDown = false;
const mouseDownEmitter = addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => {
isMouseDown = true;
hideHover(true, true);
}, true);
const mouseUpEmitter = addDisposableListener(targetElement, EventType.MOUSE_UP, () => {
isMouseDown = false;
}, true);
const mouseLeaveEmitter = addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e) => {
isMouseDown = false;
hideHover(false, e.fromElement === targetElement);
}, true);
const onMouseOver = (e) => {
if (hoverPreparation) {
return;
}
const toDispose = new DisposableStore();
const target = {
targetElements: [targetElement],
dispose: () => { }
};
if (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse') {
// track the mouse position
const onMouseMove = (e) => {
target.x = e.x + 10;
if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, targetElement) !== targetElement) {
hideHover(true, true);
}
};
toDispose.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true));
}
hoverPreparation = toDispose;
if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, targetElement) !== targetElement) {
return; // Do not show hover when the mouse is over another hover target
}
toDispose.add(triggerShowHover(hoverDelegate.delay, false, target));
};
const mouseOverDomEmitter = addDisposableListener(targetElement, EventType.MOUSE_OVER, onMouseOver, true);
const onFocus = () => {
if (isMouseDown || hoverPreparation) {
return;
}
const target = {
targetElements: [targetElement],
dispose: () => { }
};
const toDispose = new DisposableStore();
const onBlur = () => hideHover(true, true);
toDispose.add(addDisposableListener(targetElement, EventType.BLUR, onBlur, true));
toDispose.add(triggerShowHover(hoverDelegate.delay, false, target));
hoverPreparation = toDispose;
};
// Do not show hover when focusing an input or textarea
let focusDomEmitter;
const tagName = targetElement.tagName.toLowerCase();
if (tagName !== 'input' && tagName !== 'textarea') {
focusDomEmitter = addDisposableListener(targetElement, EventType.FOCUS, onFocus, true);
}
const hover = {
show: focus => {
hideHover(false, true); // terminate a ongoing mouse over preparation
triggerShowHover(0, focus, undefined, focus); // show hover immediately
},
hide: () => {
hideHover(true, true);
},
update: async (newContent, hoverOptions) => {
content = newContent;
await hoverWidget?.update(content, undefined, hoverOptions);
},
dispose: () => {
this._managedHovers.delete(targetElement);
mouseOverDomEmitter.dispose();
mouseLeaveEmitter.dispose();
mouseDownEmitter.dispose();
mouseUpEmitter.dispose();
focusDomEmitter?.dispose();
hideHover(true, true);
}
};
this._managedHovers.set(targetElement, hover);
return hover;
}
showManagedHover(target) {
const hover = this._managedHovers.get(target);
if (hover) {
hover.show(true);
}
}
dispose() {
this._managedHovers.forEach(hover => hover.dispose());
super.dispose();
}
};
HoverService = __decorate([
__param(0, IInstantiationService),
__param(1, IContextMenuService),
__param(2, IKeybindingService),
__param(3, ILayoutService),
__param(4, IAccessibilityService)
], HoverService);
export { HoverService };
function getHoverOptionsIdentity(options) {
if (options === undefined) {
return undefined;
}
return options?.id ?? options;
}
class HoverContextViewDelegate {
get anchorPosition() {
return this._hover.anchor;
}
constructor(_hover, _focus = false) {
this._hover = _hover;
this._focus = _focus;
// Render over all other context views
this.layer = 1;
}
render(container) {
this._hover.render(container);
if (this._focus) {
this._hover.focus();
}
return this._hover;
}
getAnchor() {
return {
x: this._hover.x,
y: this._hover.y
};
}
layout() {
this._hover.layout();
}
}
function getHoverTargetElement(element, stopElement) {
stopElement = stopElement ?? getWindow(element).document.body;
while (!element.hasAttribute('custom-hover') && element !== stopElement) {
element = element.parentElement;
}
return element;
}
registerSingleton(IHoverService, HoverService, 1 /* InstantiationType.Delayed */);
registerThemingParticipant((theme, collector) => {
const hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-workbench .workbench-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
collector.addRule(`.monaco-workbench .workbench-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
}
});
@@ -0,0 +1,590 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import './hover.css';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { Emitter } from '../../../../base/common/event.js';
import * as dom from '../../../../base/browser/dom.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js';
import { HoverAction, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { Widget } from '../../../../base/browser/ui/widget.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { MarkdownRenderer, openLinkFromMarkdown } from '../../widget/markdownRenderer/browser/markdownRenderer.js';
import { isMarkdownString } from '../../../../base/common/htmlContent.js';
import { localize } from '../../../../nls.js';
import { isMacintosh } from '../../../../base/common/platform.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { status } from '../../../../base/browser/ui/aria/aria.js';
const $ = dom.$;
let HoverWidget = class HoverWidget extends Widget {
get _targetWindow() {
return dom.getWindow(this._target.targetElements[0]);
}
get _targetDocumentElement() {
return dom.getWindow(this._target.targetElements[0]).document.documentElement;
}
get isDisposed() { return this._isDisposed; }
get isMouseIn() { return this._lockMouseTracker.isMouseIn; }
get domNode() { return this._hover.containerDomNode; }
get onDispose() { return this._onDispose.event; }
get onRequestLayout() { return this._onRequestLayout.event; }
get anchor() { return this._hoverPosition === 2 /* HoverPosition.BELOW */ ? 0 /* AnchorPosition.BELOW */ : 1 /* AnchorPosition.ABOVE */; }
get x() { return this._x; }
get y() { return this._y; }
/**
* Whether the hover is "locked" by holding the alt/option key. When locked, the hover will not
* hide and can be hovered regardless of whether the `hideOnHover` hover option is set.
*/
get isLocked() { return this._isLocked; }
set isLocked(value) {
if (this._isLocked === value) {
return;
}
this._isLocked = value;
this._hoverContainer.classList.toggle('locked', this._isLocked);
}
constructor(options, _keybindingService, _configurationService, _openerService, _instantiationService, _accessibilityService) {
super();
this._keybindingService = _keybindingService;
this._configurationService = _configurationService;
this._openerService = _openerService;
this._instantiationService = _instantiationService;
this._accessibilityService = _accessibilityService;
this._messageListeners = new DisposableStore();
this._isDisposed = false;
this._forcePosition = false;
this._x = 0;
this._y = 0;
this._isLocked = false;
this._enableFocusTraps = false;
this._addedFocusTrap = false;
this._onDispose = this._register(new Emitter());
this._onRequestLayout = this._register(new Emitter());
this._linkHandler = options.linkHandler || (url => {
return openLinkFromMarkdown(this._openerService, url, isMarkdownString(options.content) ? options.content.isTrusted : undefined);
});
this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target);
this._hoverPointer = options.appearance?.showPointer ? $('div.workbench-hover-pointer') : undefined;
this._hover = this._register(new BaseHoverWidget());
this._hover.containerDomNode.classList.add('workbench-hover', 'fadeIn');
if (options.appearance?.compact) {
this._hover.containerDomNode.classList.add('workbench-hover', 'compact');
}
if (options.appearance?.skipFadeInAnimation) {
this._hover.containerDomNode.classList.add('skip-fade-in');
}
if (options.additionalClasses) {
this._hover.containerDomNode.classList.add(...options.additionalClasses);
}
if (options.position?.forcePosition) {
this._forcePosition = true;
}
if (options.trapFocus) {
this._enableFocusTraps = true;
}
this._hoverPosition = options.position?.hoverPosition ?? 3 /* HoverPosition.ABOVE */;
// Don't allow mousedown out of the widget, otherwise preventDefault will call and text will
// not be selected.
this.onmousedown(this._hover.containerDomNode, e => e.stopPropagation());
// Hide hover on escape
this.onkeydown(this._hover.containerDomNode, e => {
if (e.equals(9 /* KeyCode.Escape */)) {
this.dispose();
}
});
// Hide when the window loses focus
this._register(dom.addDisposableListener(this._targetWindow, 'blur', () => this.dispose()));
const rowElement = $('div.hover-row.markdown-hover');
const contentsElement = $('div.hover-contents');
if (typeof options.content === 'string') {
contentsElement.textContent = options.content;
contentsElement.style.whiteSpace = 'pre-wrap';
}
else if (dom.isHTMLElement(options.content)) {
contentsElement.appendChild(options.content);
contentsElement.classList.add('html-hover-contents');
}
else {
const markdown = options.content;
const mdRenderer = this._instantiationService.createInstance(MarkdownRenderer, { codeBlockFontFamily: this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily });
const { element } = mdRenderer.render(markdown, {
actionHandler: {
callback: (content) => this._linkHandler(content),
disposables: this._messageListeners
},
asyncRenderCallback: () => {
contentsElement.classList.add('code-hover-contents');
this.layout();
// This changes the dimensions of the hover so trigger a layout
this._onRequestLayout.fire();
}
});
contentsElement.appendChild(element);
}
rowElement.appendChild(contentsElement);
this._hover.contentsDomNode.appendChild(rowElement);
if (options.actions && options.actions.length > 0) {
const statusBarElement = $('div.hover-row.status-bar');
const actionsElement = $('div.actions');
options.actions.forEach(action => {
const keybinding = this._keybindingService.lookupKeybinding(action.commandId);
const keybindingLabel = keybinding ? keybinding.getLabel() : null;
HoverAction.render(actionsElement, {
label: action.label,
commandId: action.commandId,
run: e => {
action.run(e);
this.dispose();
},
iconClass: action.iconClass
}, keybindingLabel);
});
statusBarElement.appendChild(actionsElement);
this._hover.containerDomNode.appendChild(statusBarElement);
}
this._hoverContainer = $('div.workbench-hover-container');
if (this._hoverPointer) {
this._hoverContainer.appendChild(this._hoverPointer);
}
this._hoverContainer.appendChild(this._hover.containerDomNode);
// Determine whether to hide on hover
let hideOnHover;
if (options.actions && options.actions.length > 0) {
// If there are actions, require hover so they can be accessed
hideOnHover = false;
}
else {
if (options.persistence?.hideOnHover === undefined) {
// When unset, will default to true when it's a string or when it's markdown that
// appears to have a link using a naive check for '](' and '</a>'
hideOnHover = typeof options.content === 'string' ||
isMarkdownString(options.content) && !options.content.value.includes('](') && !options.content.value.includes('</a>');
}
else {
// It's set explicitly
hideOnHover = options.persistence.hideOnHover;
}
}
// Show the hover hint if needed
if (hideOnHover && options.appearance?.showHoverHint) {
const statusBarElement = $('div.hover-row.status-bar');
const infoElement = $('div.info');
infoElement.textContent = localize('hoverhint', 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt');
statusBarElement.appendChild(infoElement);
this._hover.containerDomNode.appendChild(statusBarElement);
}
const mouseTrackerTargets = [...this._target.targetElements];
if (!hideOnHover) {
mouseTrackerTargets.push(this._hoverContainer);
}
const mouseTracker = this._register(new CompositeMouseTracker(mouseTrackerTargets));
this._register(mouseTracker.onMouseOut(() => {
if (!this._isLocked) {
this.dispose();
}
}));
// Setup another mouse tracker when hideOnHover is set in order to track the hover as well
// when it is locked. This ensures the hover will hide on mouseout after alt has been
// released to unlock the element.
if (hideOnHover) {
const mouseTracker2Targets = [...this._target.targetElements, this._hoverContainer];
this._lockMouseTracker = this._register(new CompositeMouseTracker(mouseTracker2Targets));
this._register(this._lockMouseTracker.onMouseOut(() => {
if (!this._isLocked) {
this.dispose();
}
}));
}
else {
this._lockMouseTracker = mouseTracker;
}
}
addFocusTrap() {
if (!this._enableFocusTraps || this._addedFocusTrap) {
return;
}
this._addedFocusTrap = true;
// Add a hover tab loop if the hover has at least one element with a valid tabIndex
const firstContainerFocusElement = this._hover.containerDomNode;
const lastContainerFocusElement = this.findLastFocusableChild(this._hover.containerDomNode);
if (lastContainerFocusElement) {
const beforeContainerFocusElement = dom.prepend(this._hoverContainer, $('div'));
const afterContainerFocusElement = dom.append(this._hoverContainer, $('div'));
beforeContainerFocusElement.tabIndex = 0;
afterContainerFocusElement.tabIndex = 0;
this._register(dom.addDisposableListener(afterContainerFocusElement, 'focus', (e) => {
firstContainerFocusElement.focus();
e.preventDefault();
}));
this._register(dom.addDisposableListener(beforeContainerFocusElement, 'focus', (e) => {
lastContainerFocusElement.focus();
e.preventDefault();
}));
}
}
findLastFocusableChild(root) {
if (root.hasChildNodes()) {
for (let i = 0; i < root.childNodes.length; i++) {
const node = root.childNodes.item(root.childNodes.length - i - 1);
if (node.nodeType === node.ELEMENT_NODE) {
const parsedNode = node;
if (typeof parsedNode.tabIndex === 'number' && parsedNode.tabIndex >= 0) {
return parsedNode;
}
}
const recursivelyFoundElement = this.findLastFocusableChild(node);
if (recursivelyFoundElement) {
return recursivelyFoundElement;
}
}
}
return undefined;
}
render(container) {
container.appendChild(this._hoverContainer);
const hoverFocused = this._hoverContainer.contains(this._hoverContainer.ownerDocument.activeElement);
const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel());
if (accessibleViewHint) {
status(accessibleViewHint);
}
this.layout();
this.addFocusTrap();
}
layout() {
this._hover.containerDomNode.classList.remove('right-aligned');
this._hover.contentsDomNode.style.maxHeight = '';
const getZoomAccountedBoundingClientRect = (e) => {
const zoom = dom.getDomNodeZoomLevel(e);
const boundingRect = e.getBoundingClientRect();
return {
top: boundingRect.top * zoom,
bottom: boundingRect.bottom * zoom,
right: boundingRect.right * zoom,
left: boundingRect.left * zoom,
};
};
const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e));
const { top, right, bottom, left } = targetBounds[0];
const width = right - left;
const height = bottom - top;
const targetRect = {
top, right, bottom, left, width, height,
center: {
x: left + (width / 2),
y: top + (height / 2)
}
};
// These calls adjust the position depending on spacing.
this.adjustHorizontalHoverPosition(targetRect);
this.adjustVerticalHoverPosition(targetRect);
// This call limits the maximum height of the hover.
this.adjustHoverMaxHeight(targetRect);
// Offset the hover position if there is a pointer so it aligns with the target element
this._hoverContainer.style.padding = '';
this._hoverContainer.style.margin = '';
if (this._hoverPointer) {
switch (this._hoverPosition) {
case 1 /* HoverPosition.RIGHT */:
targetRect.left += 3 /* Constants.PointerSize */;
targetRect.right += 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingLeft = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginLeft = `${-3 /* Constants.PointerSize */}px`;
break;
case 0 /* HoverPosition.LEFT */:
targetRect.left -= 3 /* Constants.PointerSize */;
targetRect.right -= 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingRight = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginRight = `${-3 /* Constants.PointerSize */}px`;
break;
case 2 /* HoverPosition.BELOW */:
targetRect.top += 3 /* Constants.PointerSize */;
targetRect.bottom += 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingTop = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginTop = `${-3 /* Constants.PointerSize */}px`;
break;
case 3 /* HoverPosition.ABOVE */:
targetRect.top -= 3 /* Constants.PointerSize */;
targetRect.bottom -= 3 /* Constants.PointerSize */;
this._hoverContainer.style.paddingBottom = `${3 /* Constants.PointerSize */}px`;
this._hoverContainer.style.marginBottom = `${-3 /* Constants.PointerSize */}px`;
break;
}
targetRect.center.x = targetRect.left + (width / 2);
targetRect.center.y = targetRect.top + (height / 2);
}
this.computeXCordinate(targetRect);
this.computeYCordinate(targetRect);
if (this._hoverPointer) {
// reset
this._hoverPointer.classList.remove('top');
this._hoverPointer.classList.remove('left');
this._hoverPointer.classList.remove('right');
this._hoverPointer.classList.remove('bottom');
this.setHoverPointerPosition(targetRect);
}
this._hover.onContentsChanged();
}
computeXCordinate(target) {
const hoverWidth = this._hover.containerDomNode.clientWidth + 2 /* Constants.HoverBorderWidth */;
if (this._target.x !== undefined) {
this._x = this._target.x;
}
else if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
this._x = target.right;
}
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
this._x = target.left - hoverWidth;
}
else {
if (this._hoverPointer) {
this._x = target.center.x - (this._hover.containerDomNode.clientWidth / 2);
}
else {
this._x = target.left;
}
// Hover is going beyond window towards right end
if (this._x + hoverWidth >= this._targetDocumentElement.clientWidth) {
this._hover.containerDomNode.classList.add('right-aligned');
this._x = Math.max(this._targetDocumentElement.clientWidth - hoverWidth - 2 /* Constants.HoverWindowEdgeMargin */, this._targetDocumentElement.clientLeft);
}
}
// Hover is going beyond window towards left end
if (this._x < this._targetDocumentElement.clientLeft) {
this._x = target.left + 2 /* Constants.HoverWindowEdgeMargin */;
}
}
computeYCordinate(target) {
if (this._target.y !== undefined) {
this._y = this._target.y;
}
else if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
this._y = target.top;
}
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
this._y = target.bottom - 2;
}
else {
if (this._hoverPointer) {
this._y = target.center.y + (this._hover.containerDomNode.clientHeight / 2);
}
else {
this._y = target.bottom;
}
}
// Hover on bottom is going beyond window
if (this._y > this._targetWindow.innerHeight) {
this._y = target.bottom;
}
}
adjustHorizontalHoverPosition(target) {
// Do not adjust horizontal hover position if x cordiante is provided
if (this._target.x !== undefined) {
return;
}
const hoverPointerOffset = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0);
// When force position is enabled, restrict max width
if (this._forcePosition) {
const padding = hoverPointerOffset + 2 /* Constants.HoverBorderWidth */;
if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
this._hover.containerDomNode.style.maxWidth = `${this._targetDocumentElement.clientWidth - target.right - padding}px`;
}
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
this._hover.containerDomNode.style.maxWidth = `${target.left - padding}px`;
}
return;
}
// Position hover on right to target
if (this._hoverPosition === 1 /* HoverPosition.RIGHT */) {
const roomOnRight = this._targetDocumentElement.clientWidth - target.right;
// Hover on the right is going beyond window.
if (roomOnRight < this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
const roomOnLeft = target.left;
// There's enough room on the left, flip the hover position
if (roomOnLeft >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
this._hoverPosition = 0 /* HoverPosition.LEFT */;
}
// Hover on the left would go beyond window too
else {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
}
// Position hover on left to target
else if (this._hoverPosition === 0 /* HoverPosition.LEFT */) {
const roomOnLeft = target.left;
// Hover on the left is going beyond window.
if (roomOnLeft < this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
const roomOnRight = this._targetDocumentElement.clientWidth - target.right;
// There's enough room on the right, flip the hover position
if (roomOnRight >= this._hover.containerDomNode.clientWidth + hoverPointerOffset) {
this._hoverPosition = 1 /* HoverPosition.RIGHT */;
}
// Hover on the right would go beyond window too
else {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
// Hover on the left is going beyond window.
if (target.left - this._hover.containerDomNode.clientWidth - hoverPointerOffset <= this._targetDocumentElement.clientLeft) {
this._hoverPosition = 1 /* HoverPosition.RIGHT */;
}
}
}
adjustVerticalHoverPosition(target) {
// Do not adjust vertical hover position if the y coordinate is provided
// or the position is forced
if (this._target.y !== undefined || this._forcePosition) {
return;
}
const hoverPointerOffset = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0);
// Position hover on top of the target
if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
// Hover on top is going beyond window
if (target.top - this._hover.containerDomNode.clientHeight - hoverPointerOffset < 0) {
this._hoverPosition = 2 /* HoverPosition.BELOW */;
}
}
// Position hover below the target
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
// Hover on bottom is going beyond window
if (target.bottom + this._hover.containerDomNode.clientHeight + hoverPointerOffset > this._targetWindow.innerHeight) {
this._hoverPosition = 3 /* HoverPosition.ABOVE */;
}
}
}
adjustHoverMaxHeight(target) {
let maxHeight = this._targetWindow.innerHeight / 2;
// When force position is enabled, restrict max height
if (this._forcePosition) {
const padding = (this._hoverPointer ? 3 /* Constants.PointerSize */ : 0) + 2 /* Constants.HoverBorderWidth */;
if (this._hoverPosition === 3 /* HoverPosition.ABOVE */) {
maxHeight = Math.min(maxHeight, target.top - padding);
}
else if (this._hoverPosition === 2 /* HoverPosition.BELOW */) {
maxHeight = Math.min(maxHeight, this._targetWindow.innerHeight - target.bottom - padding);
}
}
this._hover.containerDomNode.style.maxHeight = `${maxHeight}px`;
if (this._hover.contentsDomNode.clientHeight < this._hover.contentsDomNode.scrollHeight) {
// Add padding for a vertical scrollbar
const extraRightPadding = `${this._hover.scrollbar.options.verticalScrollbarSize}px`;
if (this._hover.contentsDomNode.style.paddingRight !== extraRightPadding) {
this._hover.contentsDomNode.style.paddingRight = extraRightPadding;
}
}
}
setHoverPointerPosition(target) {
if (!this._hoverPointer) {
return;
}
switch (this._hoverPosition) {
case 0 /* HoverPosition.LEFT */:
case 1 /* HoverPosition.RIGHT */: {
this._hoverPointer.classList.add(this._hoverPosition === 0 /* HoverPosition.LEFT */ ? 'right' : 'left');
const hoverHeight = this._hover.containerDomNode.clientHeight;
// If hover is taller than target, then show the pointer at the center of target
if (hoverHeight > target.height) {
this._hoverPointer.style.top = `${target.center.y - (this._y - hoverHeight) - 3 /* Constants.PointerSize */}px`;
}
// Otherwise show the pointer at the center of hover
else {
this._hoverPointer.style.top = `${Math.round((hoverHeight / 2)) - 3 /* Constants.PointerSize */}px`;
}
break;
}
case 3 /* HoverPosition.ABOVE */:
case 2 /* HoverPosition.BELOW */: {
this._hoverPointer.classList.add(this._hoverPosition === 3 /* HoverPosition.ABOVE */ ? 'bottom' : 'top');
const hoverWidth = this._hover.containerDomNode.clientWidth;
// Position pointer at the center of the hover
let pointerLeftPosition = Math.round((hoverWidth / 2)) - 3 /* Constants.PointerSize */;
// If pointer goes beyond target then position it at the center of the target
const pointerX = this._x + pointerLeftPosition;
if (pointerX < target.left || pointerX > target.right) {
pointerLeftPosition = target.center.x - this._x - 3 /* Constants.PointerSize */;
}
this._hoverPointer.style.left = `${pointerLeftPosition}px`;
break;
}
}
}
focus() {
this._hover.containerDomNode.focus();
}
dispose() {
if (!this._isDisposed) {
this._onDispose.fire();
this._hoverContainer.remove();
this._messageListeners.dispose();
this._target.dispose();
super.dispose();
}
this._isDisposed = true;
}
};
HoverWidget = __decorate([
__param(1, IKeybindingService),
__param(2, IConfigurationService),
__param(3, IOpenerService),
__param(4, IInstantiationService),
__param(5, IAccessibilityService)
], HoverWidget);
export { HoverWidget };
class CompositeMouseTracker extends Widget {
get onMouseOut() { return this._onMouseOut.event; }
get isMouseIn() { return this._isMouseIn; }
constructor(_elements) {
super();
this._elements = _elements;
this._isMouseIn = true;
this._onMouseOut = this._register(new Emitter());
this._elements.forEach(n => this.onmouseover(n, () => this._onTargetMouseOver(n)));
this._elements.forEach(n => this.onmouseleave(n, () => this._onTargetMouseLeave(n)));
}
_onTargetMouseOver(target) {
this._isMouseIn = true;
this._clearEvaluateMouseStateTimeout(target);
}
_onTargetMouseLeave(target) {
this._isMouseIn = false;
this._evaluateMouseState(target);
}
_evaluateMouseState(target) {
this._clearEvaluateMouseStateTimeout(target);
// Evaluate whether the mouse is still outside asynchronously such that other mouse targets
// have the opportunity to first their mouse in event.
this._mouseTimeout = dom.getWindow(target).setTimeout(() => this._fireIfMouseOutside(), 0);
}
_clearEvaluateMouseStateTimeout(target) {
if (this._mouseTimeout) {
dom.getWindow(target).clearTimeout(this._mouseTimeout);
this._mouseTimeout = undefined;
}
}
_fireIfMouseOutside() {
if (!this._isMouseIn) {
this._onMouseOut.fire();
}
}
}
class ElementHoverTarget {
constructor(_element) {
this._element = _element;
this.targetElements = [this._element];
}
dispose() {
}
}
@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isHTMLElement } from '../../../../base/browser/dom.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { isMarkdownString } from '../../../../base/common/htmlContent.js';
import { isFunction, isString } from '../../../../base/common/types.js';
import { localize } from '../../../../nls.js';
export class ManagedHoverWidget {
constructor(hoverDelegate, target, fadeInAnimation) {
this.hoverDelegate = hoverDelegate;
this.target = target;
this.fadeInAnimation = fadeInAnimation;
}
async update(content, focus, options) {
if (this._cancellationTokenSource) {
// there's an computation ongoing, cancel it
this._cancellationTokenSource.dispose(true);
this._cancellationTokenSource = undefined;
}
if (this.isDisposed) {
return;
}
let resolvedContent;
if (content === undefined || isString(content) || isHTMLElement(content)) {
resolvedContent = content;
}
else if (!isFunction(content.markdown)) {
resolvedContent = content.markdown ?? content.markdownNotSupportedFallback;
}
else {
// compute the content, potentially long-running
// show 'Loading' if no hover is up yet
if (!this._hoverWidget) {
this.show(localize('iconLabel.loading', "Loading..."), focus, options);
}
// compute the content
this._cancellationTokenSource = new CancellationTokenSource();
const token = this._cancellationTokenSource.token;
resolvedContent = await content.markdown(token);
if (resolvedContent === undefined) {
resolvedContent = content.markdownNotSupportedFallback;
}
if (this.isDisposed || token.isCancellationRequested) {
// either the widget has been closed in the meantime
// or there has been a new call to `update`
return;
}
}
this.show(resolvedContent, focus, options);
}
show(content, focus, options) {
const oldHoverWidget = this._hoverWidget;
if (this.hasContent(content)) {
const hoverOptions = {
content,
target: this.target,
appearance: {
showPointer: this.hoverDelegate.placement === 'element',
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing
},
position: {
hoverPosition: 2 /* HoverPosition.BELOW */,
},
...options
};
this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
}
oldHoverWidget?.dispose();
}
hasContent(content) {
if (!content) {
return false;
}
if (isMarkdownString(content)) {
return !!content.value;
}
return true;
}
get isDisposed() {
return this._hoverWidget?.isDisposed;
}
dispose() {
this._hoverWidget?.dispose();
this._cancellationTokenSource?.dispose(true);
this._cancellationTokenSource = undefined;
}
}
@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { IMarkerDecorationsService } from '../../common/services/markerDecorations.js';
import { registerEditorContribution } from '../editorExtensions.js';
let MarkerDecorationsContribution = class MarkerDecorationsContribution {
static { this.ID = 'editor.contrib.markerDecorations'; }
constructor(_editor, _markerDecorationsService) {
// Doesn't do anything, just requires `IMarkerDecorationsService` to make sure it gets instantiated
}
dispose() {
}
};
MarkerDecorationsContribution = __decorate([
__param(1, IMarkerDecorationsService)
], MarkerDecorationsContribution);
export { MarkerDecorationsContribution };
registerEditorContribution(MarkerDecorationsContribution.ID, MarkerDecorationsContribution, 0 /* EditorContributionInstantiation.Eager */); // eager because it instantiates IMarkerDecorationsService which is responsible for rendering squiggles
@@ -0,0 +1,219 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as dom from '../../../base/browser/dom.js';
import { mainWindow } from '../../../base/browser/window.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { LinkedList } from '../../../base/common/linkedList.js';
import { ResourceMap } from '../../../base/common/map.js';
import { parse } from '../../../base/common/marshalling.js';
import { matchesScheme, matchesSomeScheme, Schemas } from '../../../base/common/network.js';
import { normalizePath } from '../../../base/common/resources.js';
import { URI } from '../../../base/common/uri.js';
import { ICodeEditorService } from './codeEditorService.js';
import { ICommandService } from '../../../platform/commands/common/commands.js';
import { EditorOpenSource } from '../../../platform/editor/common/editor.js';
import { extractSelection } from '../../../platform/opener/common/opener.js';
let CommandOpener = class CommandOpener {
constructor(_commandService) {
this._commandService = _commandService;
}
async open(target, options) {
if (!matchesScheme(target, Schemas.command)) {
return false;
}
if (!options?.allowCommands) {
// silently ignore commands when command-links are disabled, also
// suppress other openers by returning TRUE
return true;
}
if (typeof target === 'string') {
target = URI.parse(target);
}
if (Array.isArray(options.allowCommands)) {
// Only allow specific commands
if (!options.allowCommands.includes(target.path)) {
// Suppress other openers by returning TRUE
return true;
}
}
// execute as command
let args = [];
try {
args = parse(decodeURIComponent(target.query));
}
catch {
// ignore and retry
try {
args = parse(target.query);
}
catch {
// ignore error
}
}
if (!Array.isArray(args)) {
args = [args];
}
await this._commandService.executeCommand(target.path, ...args);
return true;
}
};
CommandOpener = __decorate([
__param(0, ICommandService)
], CommandOpener);
let EditorOpener = class EditorOpener {
constructor(_editorService) {
this._editorService = _editorService;
}
async open(target, options) {
if (typeof target === 'string') {
target = URI.parse(target);
}
const { selection, uri } = extractSelection(target);
target = uri;
if (target.scheme === Schemas.file) {
target = normalizePath(target); // workaround for non-normalized paths (https://github.com/microsoft/vscode/issues/12954)
}
await this._editorService.openCodeEditor({
resource: target,
options: {
selection,
source: options?.fromUserGesture ? EditorOpenSource.USER : EditorOpenSource.API,
...options?.editorOptions
}
}, this._editorService.getFocusedCodeEditor(), options?.openToSide);
return true;
}
};
EditorOpener = __decorate([
__param(0, ICodeEditorService)
], EditorOpener);
let OpenerService = class OpenerService {
constructor(editorService, commandService) {
this._openers = new LinkedList();
this._validators = new LinkedList();
this._resolvers = new LinkedList();
this._resolvedUriTargets = new ResourceMap(uri => uri.with({ path: null, fragment: null, query: null }).toString());
this._externalOpeners = new LinkedList();
// Default external opener is going through window.open()
this._defaultExternalOpener = {
openExternal: async (href) => {
// ensure to open HTTP/HTTPS links into new windows
// to not trigger a navigation. Any other link is
// safe to be set as HREF to prevent a blank window
// from opening.
if (matchesSomeScheme(href, Schemas.http, Schemas.https)) {
dom.windowOpenNoOpener(href);
}
else {
mainWindow.location.href = href;
}
return true;
}
};
// Default opener: any external, maito, http(s), command, and catch-all-editors
this._openers.push({
open: async (target, options) => {
if (options?.openExternal || matchesSomeScheme(target, Schemas.mailto, Schemas.http, Schemas.https, Schemas.vsls)) {
// open externally
await this._doOpenExternal(target, options);
return true;
}
return false;
}
});
this._openers.push(new CommandOpener(commandService));
this._openers.push(new EditorOpener(editorService));
}
registerOpener(opener) {
const remove = this._openers.unshift(opener);
return { dispose: remove };
}
async open(target, options) {
// check with contributed validators
const targetURI = typeof target === 'string' ? URI.parse(target) : target;
// validate against the original URI that this URI resolves to, if one exists
const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target;
for (const validator of this._validators) {
if (!(await validator.shouldOpen(validationTarget, options))) {
return false;
}
}
// check with contributed openers
for (const opener of this._openers) {
const handled = await opener.open(target, options);
if (handled) {
return true;
}
}
return false;
}
async resolveExternalUri(resource, options) {
for (const resolver of this._resolvers) {
try {
const result = await resolver.resolveExternalUri(resource, options);
if (result) {
if (!this._resolvedUriTargets.has(result.resolved)) {
this._resolvedUriTargets.set(result.resolved, resource);
}
return result;
}
}
catch {
// noop
}
}
throw new Error('Could not resolve external URI: ' + resource.toString());
}
async _doOpenExternal(resource, options) {
//todo@jrieken IExternalUriResolver should support `uri: URI | string`
const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
let externalUri;
try {
externalUri = (await this.resolveExternalUri(uri, options)).resolved;
}
catch {
externalUri = uri;
}
let href;
if (typeof resource === 'string' && uri.toString() === externalUri.toString()) {
// open the url-string AS IS
href = resource;
}
else {
// open URI using the toString(noEncode)+encodeURI-trick
href = encodeURI(externalUri.toString(true));
}
if (options?.allowContributedOpeners) {
const preferredOpenerId = typeof options?.allowContributedOpeners === 'string' ? options?.allowContributedOpeners : undefined;
for (const opener of this._externalOpeners) {
const didOpen = await opener.openExternal(href, {
sourceUri: uri,
preferredOpenerId,
}, CancellationToken.None);
if (didOpen) {
return true;
}
}
}
return this._defaultExternalOpener.openExternal(href, { sourceUri: uri }, CancellationToken.None);
}
dispose() {
this._validators.clear();
}
};
OpenerService = __decorate([
__param(0, ICodeEditorService),
__param(1, ICommandService)
], OpenerService);
export { OpenerService };
@@ -0,0 +1,65 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getAllMethodNames } from '../../../base/common/objects.js';
import { EditorWorkerClient } from './editorWorkerService.js';
/**
* Create a new web worker that has model syncing capabilities built in.
* Specify an AMD module to load that will `create` an object that will be proxied.
*/
export function createWebWorker(modelService, languageConfigurationService, opts) {
return new MonacoWebWorkerImpl(modelService, languageConfigurationService, opts);
}
class MonacoWebWorkerImpl extends EditorWorkerClient {
constructor(modelService, languageConfigurationService, opts) {
super(modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService);
this._foreignModuleId = opts.moduleId;
this._foreignModuleCreateData = opts.createData || null;
this._foreignModuleHost = opts.host || null;
this._foreignProxy = null;
}
// foreign host request
fhr(method, args) {
if (!this._foreignModuleHost || typeof this._foreignModuleHost[method] !== 'function') {
return Promise.reject(new Error('Missing method ' + method + ' or missing main thread foreign host.'));
}
try {
return Promise.resolve(this._foreignModuleHost[method].apply(this._foreignModuleHost, args));
}
catch (e) {
return Promise.reject(e);
}
}
_getForeignProxy() {
if (!this._foreignProxy) {
this._foreignProxy = this._getProxy().then((proxy) => {
const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : [];
return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => {
this._foreignModuleCreateData = null;
const proxyMethodRequest = (method, args) => {
return proxy.fmr(method, args);
};
const createProxyMethod = (method, proxyMethodRequest) => {
return function () {
const args = Array.prototype.slice.call(arguments, 0);
return proxyMethodRequest(method, args);
};
};
const foreignProxy = {};
for (const foreignMethod of foreignMethods) {
foreignProxy[foreignMethod] = createProxyMethod(foreignMethod, proxyMethodRequest);
}
return foreignProxy;
});
});
}
return this._foreignProxy;
}
getProxy() {
return this._getForeignProxy();
}
withSyncedResources(resources) {
return this._withSyncedResources(resources).then(_ => this.getProxy());
}
}
@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class StableEditorScrollState {
static capture(editor) {
if (editor.getScrollTop() === 0 || editor.hasPendingScrollAnimation()) {
// Never mess with the scroll top if the editor is at the top of the file or if there is a pending scroll animation
return new StableEditorScrollState(editor.getScrollTop(), editor.getContentHeight(), null, 0, null);
}
let visiblePosition = null;
let visiblePositionScrollDelta = 0;
const visibleRanges = editor.getVisibleRanges();
if (visibleRanges.length > 0) {
visiblePosition = visibleRanges[0].getStartPosition();
const visiblePositionScrollTop = editor.getTopForPosition(visiblePosition.lineNumber, visiblePosition.column);
visiblePositionScrollDelta = editor.getScrollTop() - visiblePositionScrollTop;
}
return new StableEditorScrollState(editor.getScrollTop(), editor.getContentHeight(), visiblePosition, visiblePositionScrollDelta, editor.getPosition());
}
constructor(_initialScrollTop, _initialContentHeight, _visiblePosition, _visiblePositionScrollDelta, _cursorPosition) {
this._initialScrollTop = _initialScrollTop;
this._initialContentHeight = _initialContentHeight;
this._visiblePosition = _visiblePosition;
this._visiblePositionScrollDelta = _visiblePositionScrollDelta;
this._cursorPosition = _cursorPosition;
}
restore(editor) {
if (this._initialContentHeight === editor.getContentHeight() && this._initialScrollTop === editor.getScrollTop()) {
// The editor's content height and scroll top haven't changed, so we don't need to do anything
return;
}
if (this._visiblePosition) {
const visiblePositionScrollTop = editor.getTopForPosition(this._visiblePosition.lineNumber, this._visiblePosition.column);
editor.setScrollTop(visiblePositionScrollTop + this._visiblePositionScrollDelta);
}
}
restoreRelativeVerticalPositionOfCursor(editor) {
if (this._initialContentHeight === editor.getContentHeight() && this._initialScrollTop === editor.getScrollTop()) {
// The editor's content height and scroll top haven't changed, so we don't need to do anything
return;
}
const currentCursorPosition = editor.getPosition();
if (!this._cursorPosition || !currentCursorPosition) {
return;
}
const offset = editor.getTopForLineNumber(currentCursorPosition.lineNumber) - editor.getTopForLineNumber(this._cursorPosition.lineNumber);
editor.setScrollTop(editor.getScrollTop() + offset, 1 /* ScrollType.Immediate */);
}
}
+597
View File
@@ -0,0 +1,597 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as dom from '../../base/browser/dom.js';
import { createFastDomNode } from '../../base/browser/fastDomNode.js';
import { inputLatency } from '../../base/browser/performance.js';
import { BugIndicatingError, onUnexpectedError } from '../../base/common/errors.js';
import { PointerHandlerLastRenderData } from './controller/mouseTarget.js';
import { PointerHandler } from './controller/pointerHandler.js';
import { TextAreaHandler } from './controller/textAreaHandler.js';
import { RenderingContext } from './view/renderingContext.js';
import { ViewController } from './view/viewController.js';
import { ContentViewOverlays, MarginViewOverlays } from './view/viewOverlays.js';
import { PartFingerprints } from './view/viewPart.js';
import { ViewUserInputEvents } from './view/viewUserInputEvents.js';
import { BlockDecorations } from './viewParts/blockDecorations/blockDecorations.js';
import { ViewContentWidgets } from './viewParts/contentWidgets/contentWidgets.js';
import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from './viewParts/currentLineHighlight/currentLineHighlight.js';
import { DecorationsOverlay } from './viewParts/decorations/decorations.js';
import { EditorScrollbar } from './viewParts/editorScrollbar/editorScrollbar.js';
import { GlyphMarginWidgets } from './viewParts/glyphMargin/glyphMargin.js';
import { IndentGuidesOverlay } from './viewParts/indentGuides/indentGuides.js';
import { LineNumbersOverlay } from './viewParts/lineNumbers/lineNumbers.js';
import { ViewLines } from './viewParts/lines/viewLines.js';
import { LinesDecorationsOverlay } from './viewParts/linesDecorations/linesDecorations.js';
import { Margin } from './viewParts/margin/margin.js';
import { MarginViewLineDecorationsOverlay } from './viewParts/marginDecorations/marginDecorations.js';
import { Minimap } from './viewParts/minimap/minimap.js';
import { ViewOverlayWidgets } from './viewParts/overlayWidgets/overlayWidgets.js';
import { DecorationsOverviewRuler } from './viewParts/overviewRuler/decorationsOverviewRuler.js';
import { OverviewRuler } from './viewParts/overviewRuler/overviewRuler.js';
import { Rulers } from './viewParts/rulers/rulers.js';
import { ScrollDecorationViewPart } from './viewParts/scrollDecoration/scrollDecoration.js';
import { SelectionsOverlay } from './viewParts/selections/selections.js';
import { ViewCursors } from './viewParts/viewCursors/viewCursors.js';
import { ViewZones } from './viewParts/viewZones/viewZones.js';
import { WhitespaceOverlay } from './viewParts/whitespace/whitespace.js';
import { Position } from '../common/core/position.js';
import { Range } from '../common/core/range.js';
import { Selection } from '../common/core/selection.js';
import { GlyphMarginLane } from '../common/model.js';
import { ViewEventHandler } from '../common/viewEventHandler.js';
import { ViewportData } from '../common/viewLayout/viewLinesViewportData.js';
import { ViewContext } from '../common/viewModel/viewContext.js';
import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
import { getThemeTypeSelector } from '../../platform/theme/common/themeService.js';
let View = class View extends ViewEventHandler {
constructor(commandDelegate, configuration, colorTheme, model, userInputEvents, overflowWidgetsDomNode, _instantiationService) {
super();
this._instantiationService = _instantiationService;
// Actual mutable state
this._shouldRecomputeGlyphMarginLanes = false;
this._selections = [new Selection(1, 1, 1, 1)];
this._renderAnimationFrame = null;
this._overflowWidgetsDomNode = overflowWidgetsDomNode ?? null;
const viewController = new ViewController(configuration, model, userInputEvents, commandDelegate);
// The view context is passed on to most classes (basically to reduce param. counts in ctors)
this._context = new ViewContext(configuration, colorTheme, model);
// Ensure the view is the first event handler in order to update the layout
this._context.addEventHandler(this);
this._viewParts = [];
// Keyboard handler
this._textAreaHandler = this._instantiationService.createInstance(TextAreaHandler, this._context, viewController, this._createTextAreaHandlerHelper());
this._viewParts.push(this._textAreaHandler);
// These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.)
this._linesContent = createFastDomNode(document.createElement('div'));
this._linesContent.setClassName('lines-content' + ' monaco-editor-background');
this._linesContent.setPosition('absolute');
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName(this._getEditorClassName());
// Set role 'code' for better screen reader support https://github.com/microsoft/vscode/issues/93438
this.domNode.setAttribute('role', 'code');
this._overflowGuardContainer = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this._overflowGuardContainer, 3 /* PartFingerprint.OverflowGuard */);
this._overflowGuardContainer.setClassName('overflow-guard');
this._scrollbar = new EditorScrollbar(this._context, this._linesContent, this.domNode, this._overflowGuardContainer);
this._viewParts.push(this._scrollbar);
// View Lines
this._viewLines = new ViewLines(this._context, this._linesContent);
// View Zones
this._viewZones = new ViewZones(this._context);
this._viewParts.push(this._viewZones);
// Decorations overview ruler
const decorationsOverviewRuler = new DecorationsOverviewRuler(this._context);
this._viewParts.push(decorationsOverviewRuler);
const scrollDecoration = new ScrollDecorationViewPart(this._context);
this._viewParts.push(scrollDecoration);
const contentViewOverlays = new ContentViewOverlays(this._context);
this._viewParts.push(contentViewOverlays);
contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context));
contentViewOverlays.addDynamicOverlay(new WhitespaceOverlay(this._context));
const marginViewOverlays = new MarginViewOverlays(this._context);
this._viewParts.push(marginViewOverlays);
marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context));
marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context));
marginViewOverlays.addDynamicOverlay(new LinesDecorationsOverlay(this._context));
marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context));
// Glyph margin widgets
this._glyphMarginWidgets = new GlyphMarginWidgets(this._context);
this._viewParts.push(this._glyphMarginWidgets);
const margin = new Margin(this._context);
margin.getDomNode().appendChild(this._viewZones.marginDomNode);
margin.getDomNode().appendChild(marginViewOverlays.getDomNode());
margin.getDomNode().appendChild(this._glyphMarginWidgets.domNode);
this._viewParts.push(margin);
// Content widgets
this._contentWidgets = new ViewContentWidgets(this._context, this.domNode);
this._viewParts.push(this._contentWidgets);
this._viewCursors = new ViewCursors(this._context);
this._viewParts.push(this._viewCursors);
// Overlay widgets
this._overlayWidgets = new ViewOverlayWidgets(this._context, this.domNode);
this._viewParts.push(this._overlayWidgets);
const rulers = new Rulers(this._context);
this._viewParts.push(rulers);
const blockOutline = new BlockDecorations(this._context);
this._viewParts.push(blockOutline);
const minimap = new Minimap(this._context);
this._viewParts.push(minimap);
// -------------- Wire dom nodes up
if (decorationsOverviewRuler) {
const overviewRulerData = this._scrollbar.getOverviewRulerLayoutInfo();
overviewRulerData.parent.insertBefore(decorationsOverviewRuler.getDomNode(), overviewRulerData.insertBefore);
}
this._linesContent.appendChild(contentViewOverlays.getDomNode());
this._linesContent.appendChild(rulers.domNode);
this._linesContent.appendChild(this._viewZones.domNode);
this._linesContent.appendChild(this._viewLines.getDomNode());
this._linesContent.appendChild(this._contentWidgets.domNode);
this._linesContent.appendChild(this._viewCursors.getDomNode());
this._overflowGuardContainer.appendChild(margin.getDomNode());
this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode());
this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode());
this._overflowGuardContainer.appendChild(this._textAreaHandler.textArea);
this._overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover);
this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode());
this._overflowGuardContainer.appendChild(minimap.getDomNode());
this._overflowGuardContainer.appendChild(blockOutline.domNode);
this.domNode.appendChild(this._overflowGuardContainer);
if (overflowWidgetsDomNode) {
overflowWidgetsDomNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode.domNode);
overflowWidgetsDomNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode.domNode);
}
else {
this.domNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode);
this.domNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode);
}
this._applyLayout();
// Pointer handler
this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper()));
}
_computeGlyphMarginLanes() {
const model = this._context.viewModel.model;
const laneModel = this._context.viewModel.glyphLanes;
let glyphs = [];
let maxLineNumber = 0;
// Add all margin decorations
glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => {
const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Center;
maxLineNumber = Math.max(maxLineNumber, decoration.range.endLineNumber);
return { range: decoration.range, lane, persist: decoration.options.glyphMargin?.persistLane };
}));
// Add all glyph margin widgets
glyphs = glyphs.concat(this._glyphMarginWidgets.getWidgets().map((widget) => {
const range = model.validateRange(widget.preference.range);
maxLineNumber = Math.max(maxLineNumber, range.endLineNumber);
return { range, lane: widget.preference.lane };
}));
// Sorted by their start position
glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
laneModel.reset(maxLineNumber);
for (const glyph of glyphs) {
laneModel.push(glyph.lane, glyph.range, glyph.persist);
}
return laneModel;
}
_createPointerHandlerHelper() {
return {
viewDomNode: this.domNode.domNode,
overflowWidgetsDomNode: this._overflowWidgetsDomNode,
linesContentDomNode: this._linesContent.domNode,
viewLinesDomNode: this._viewLines.getDomNode().domNode,
focusTextArea: () => {
this.focus();
},
dispatchTextAreaEvent: (event) => {
this._textAreaHandler.textArea.domNode.dispatchEvent(event);
},
getLastRenderData: () => {
const lastViewCursorsRenderData = this._viewCursors.getLastRenderData() || [];
const lastTextareaPosition = this._textAreaHandler.getLastRenderData();
return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition);
},
renderNow: () => {
this.render(true, false);
},
shouldSuppressMouseDownOnViewZone: (viewZoneId) => {
return this._viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
},
shouldSuppressMouseDownOnWidget: (widgetId) => {
return this._contentWidgets.shouldSuppressMouseDownOnWidget(widgetId);
},
getPositionFromDOMInfo: (spanNode, offset) => {
this._flushAccumulatedAndRenderNow();
return this._viewLines.getPositionFromDOMInfo(spanNode, offset);
},
visibleRangeForPosition: (lineNumber, column) => {
this._flushAccumulatedAndRenderNow();
return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column));
},
getLineWidth: (lineNumber) => {
this._flushAccumulatedAndRenderNow();
return this._viewLines.getLineWidth(lineNumber);
}
};
}
_createTextAreaHandlerHelper() {
return {
visibleRangeForPosition: (position) => {
this._flushAccumulatedAndRenderNow();
return this._viewLines.visibleRangeForPosition(position);
}
};
}
_applyLayout() {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this.domNode.setWidth(layoutInfo.width);
this.domNode.setHeight(layoutInfo.height);
this._overflowGuardContainer.setWidth(layoutInfo.width);
this._overflowGuardContainer.setHeight(layoutInfo.height);
// https://stackoverflow.com/questions/38905916/content-in-google-chrome-larger-than-16777216-px-not-being-rendered
this._linesContent.setWidth(16777216);
this._linesContent.setHeight(16777216);
}
_getEditorClassName() {
const focused = this._textAreaHandler.isFocused() ? ' focused' : '';
return this._context.configuration.options.get(143 /* EditorOption.editorClassName */) + ' ' + getThemeTypeSelector(this._context.theme.type) + focused;
}
// --- begin event handlers
handleEvents(events) {
super.handleEvents(events);
this._scheduleRender();
}
onConfigurationChanged(e) {
this.domNode.setClassName(this._getEditorClassName());
this._applyLayout();
return false;
}
onCursorStateChanged(e) {
this._selections = e.selections;
return false;
}
onDecorationsChanged(e) {
if (e.affectsGlyphMargin) {
this._shouldRecomputeGlyphMarginLanes = true;
}
return false;
}
onFocusChanged(e) {
this.domNode.setClassName(this._getEditorClassName());
return false;
}
onThemeChanged(e) {
this._context.theme.update(e.theme);
this.domNode.setClassName(this._getEditorClassName());
return false;
}
// --- end event handlers
dispose() {
if (this._renderAnimationFrame !== null) {
this._renderAnimationFrame.dispose();
this._renderAnimationFrame = null;
}
this._contentWidgets.overflowingContentWidgetsDomNode.domNode.remove();
this._context.removeEventHandler(this);
this._viewLines.dispose();
// Destroy view parts
for (const viewPart of this._viewParts) {
viewPart.dispose();
}
super.dispose();
}
_scheduleRender() {
if (this._store.isDisposed) {
throw new BugIndicatingError();
}
if (this._renderAnimationFrame === null) {
const rendering = this._createCoordinatedRendering();
this._renderAnimationFrame = EditorRenderingCoordinator.INSTANCE.scheduleCoordinatedRendering({
window: dom.getWindow(this.domNode.domNode),
prepareRenderText: () => {
if (this._store.isDisposed) {
throw new BugIndicatingError();
}
try {
return rendering.prepareRenderText();
}
finally {
this._renderAnimationFrame = null;
}
},
renderText: () => {
if (this._store.isDisposed) {
throw new BugIndicatingError();
}
return rendering.renderText();
},
prepareRender: (viewParts, ctx) => {
if (this._store.isDisposed) {
throw new BugIndicatingError();
}
return rendering.prepareRender(viewParts, ctx);
},
render: (viewParts, ctx) => {
if (this._store.isDisposed) {
throw new BugIndicatingError();
}
return rendering.render(viewParts, ctx);
}
});
}
}
_flushAccumulatedAndRenderNow() {
const rendering = this._createCoordinatedRendering();
safeInvokeNoArg(() => rendering.prepareRenderText());
const data = safeInvokeNoArg(() => rendering.renderText());
if (data) {
const [viewParts, ctx] = data;
safeInvokeNoArg(() => rendering.prepareRender(viewParts, ctx));
safeInvokeNoArg(() => rendering.render(viewParts, ctx));
}
}
_getViewPartsToRender() {
const result = [];
let resultLen = 0;
for (const viewPart of this._viewParts) {
if (viewPart.shouldRender()) {
result[resultLen++] = viewPart;
}
}
return result;
}
_createCoordinatedRendering() {
return {
prepareRenderText: () => {
if (this._shouldRecomputeGlyphMarginLanes) {
this._shouldRecomputeGlyphMarginLanes = false;
const model = this._computeGlyphMarginLanes();
this._context.configuration.setGlyphMarginDecorationLaneCount(model.requiredLanes);
}
inputLatency.onRenderStart();
},
renderText: () => {
if (!this.domNode.domNode.isConnected) {
return null;
}
let viewPartsToRender = this._getViewPartsToRender();
if (!this._viewLines.shouldRender() && viewPartsToRender.length === 0) {
// Nothing to render
return null;
}
const partialViewportData = this._context.viewLayout.getLinesViewportData();
this._context.viewModel.setViewport(partialViewportData.startLineNumber, partialViewportData.endLineNumber, partialViewportData.centeredLineNumber);
const viewportData = new ViewportData(this._selections, partialViewportData, this._context.viewLayout.getWhitespaceViewportData(), this._context.viewModel);
if (this._contentWidgets.shouldRender()) {
// Give the content widgets a chance to set their max width before a possible synchronous layout
this._contentWidgets.onBeforeRender(viewportData);
}
if (this._viewLines.shouldRender()) {
this._viewLines.renderText(viewportData);
this._viewLines.onDidRender();
// Rendering of viewLines might cause scroll events to occur, so collect view parts to render again
viewPartsToRender = this._getViewPartsToRender();
}
return [viewPartsToRender, new RenderingContext(this._context.viewLayout, viewportData, this._viewLines)];
},
prepareRender: (viewPartsToRender, ctx) => {
for (const viewPart of viewPartsToRender) {
viewPart.prepareRender(ctx);
}
},
render: (viewPartsToRender, ctx) => {
for (const viewPart of viewPartsToRender) {
viewPart.render(ctx);
viewPart.onDidRender();
}
}
};
}
// --- BEGIN CodeEditor helpers
delegateVerticalScrollbarPointerDown(browserEvent) {
this._scrollbar.delegateVerticalScrollbarPointerDown(browserEvent);
}
delegateScrollFromMouseWheelEvent(browserEvent) {
this._scrollbar.delegateScrollFromMouseWheelEvent(browserEvent);
}
restoreState(scrollPosition) {
this._context.viewModel.viewLayout.setScrollPosition({
scrollTop: scrollPosition.scrollTop,
scrollLeft: scrollPosition.scrollLeft
}, 1 /* ScrollType.Immediate */);
this._context.viewModel.visibleLinesStabilized();
}
getOffsetForColumn(modelLineNumber, modelColumn) {
const modelPosition = this._context.viewModel.model.validatePosition({
lineNumber: modelLineNumber,
column: modelColumn
});
const viewPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
this._flushAccumulatedAndRenderNow();
const visibleRange = this._viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column));
if (!visibleRange) {
return -1;
}
return visibleRange.left;
}
getTargetAtClientPoint(clientX, clientY) {
const mouseTarget = this._pointerHandler.getTargetAtClientPoint(clientX, clientY);
if (!mouseTarget) {
return null;
}
return ViewUserInputEvents.convertViewToModelMouseTarget(mouseTarget, this._context.viewModel.coordinatesConverter);
}
createOverviewRuler(cssClassName) {
return new OverviewRuler(this._context, cssClassName);
}
change(callback) {
this._viewZones.changeViewZones(callback);
this._scheduleRender();
}
render(now, everything) {
if (everything) {
// Force everything to render...
this._viewLines.forceShouldRender();
for (const viewPart of this._viewParts) {
viewPart.forceShouldRender();
}
}
if (now) {
this._flushAccumulatedAndRenderNow();
}
else {
this._scheduleRender();
}
}
writeScreenReaderContent(reason) {
this._textAreaHandler.writeScreenReaderContent(reason);
}
focus() {
this._textAreaHandler.focusTextArea();
}
isFocused() {
return this._textAreaHandler.isFocused();
}
setAriaOptions(options) {
this._textAreaHandler.setAriaOptions(options);
}
addContentWidget(widgetData) {
this._contentWidgets.addWidget(widgetData.widget);
this.layoutContentWidget(widgetData);
this._scheduleRender();
}
layoutContentWidget(widgetData) {
this._contentWidgets.setWidgetPosition(widgetData.widget, widgetData.position?.position ?? null, widgetData.position?.secondaryPosition ?? null, widgetData.position?.preference ?? null, widgetData.position?.positionAffinity ?? null);
this._scheduleRender();
}
removeContentWidget(widgetData) {
this._contentWidgets.removeWidget(widgetData.widget);
this._scheduleRender();
}
addOverlayWidget(widgetData) {
this._overlayWidgets.addWidget(widgetData.widget);
this.layoutOverlayWidget(widgetData);
this._scheduleRender();
}
layoutOverlayWidget(widgetData) {
const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, widgetData.position);
if (shouldRender) {
this._scheduleRender();
}
}
removeOverlayWidget(widgetData) {
this._overlayWidgets.removeWidget(widgetData.widget);
this._scheduleRender();
}
addGlyphMarginWidget(widgetData) {
this._glyphMarginWidgets.addWidget(widgetData.widget);
this._shouldRecomputeGlyphMarginLanes = true;
this._scheduleRender();
}
layoutGlyphMarginWidget(widgetData) {
const newPreference = widgetData.position;
const shouldRender = this._glyphMarginWidgets.setWidgetPosition(widgetData.widget, newPreference);
if (shouldRender) {
this._shouldRecomputeGlyphMarginLanes = true;
this._scheduleRender();
}
}
removeGlyphMarginWidget(widgetData) {
this._glyphMarginWidgets.removeWidget(widgetData.widget);
this._shouldRecomputeGlyphMarginLanes = true;
this._scheduleRender();
}
};
View = __decorate([
__param(6, IInstantiationService)
], View);
export { View };
function safeInvokeNoArg(func) {
try {
return func();
}
catch (e) {
onUnexpectedError(e);
return null;
}
}
class EditorRenderingCoordinator {
static { this.INSTANCE = new EditorRenderingCoordinator(); }
constructor() {
this._coordinatedRenderings = [];
this._animationFrameRunners = new Map();
}
scheduleCoordinatedRendering(rendering) {
this._coordinatedRenderings.push(rendering);
this._scheduleRender(rendering.window);
return {
dispose: () => {
const renderingIndex = this._coordinatedRenderings.indexOf(rendering);
if (renderingIndex === -1) {
return;
}
this._coordinatedRenderings.splice(renderingIndex, 1);
if (this._coordinatedRenderings.length === 0) {
// There are no more renderings to coordinate => cancel animation frames
for (const [_, disposable] of this._animationFrameRunners) {
disposable.dispose();
}
this._animationFrameRunners.clear();
}
}
};
}
_scheduleRender(window) {
if (!this._animationFrameRunners.has(window)) {
const runner = () => {
this._animationFrameRunners.delete(window);
this._onRenderScheduled();
};
this._animationFrameRunners.set(window, dom.runAtThisOrScheduleAtNextAnimationFrame(window, runner, 100));
}
}
_onRenderScheduled() {
const coordinatedRenderings = this._coordinatedRenderings.slice(0);
this._coordinatedRenderings = [];
for (const rendering of coordinatedRenderings) {
safeInvokeNoArg(() => rendering.prepareRenderText());
}
const datas = [];
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
const rendering = coordinatedRenderings[i];
datas[i] = safeInvokeNoArg(() => rendering.renderText());
}
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
const rendering = coordinatedRenderings[i];
const data = datas[i];
if (!data) {
continue;
}
const [viewParts, ctx] = data;
safeInvokeNoArg(() => rendering.prepareRender(viewParts, ctx));
}
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
const rendering = coordinatedRenderings[i];
const data = datas[i];
if (!data) {
continue;
}
const [viewParts, ctx] = data;
safeInvokeNoArg(() => rendering.render(viewParts, ctx));
}
}
}
@@ -0,0 +1,298 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createTrustedTypesPolicy } from '../../../base/browser/trustedTypes.js';
import * as strings from '../../../base/common/strings.js';
import { assertIsDefined } from '../../../base/common/types.js';
import { applyFontInfo } from '../config/domFontInfo.js';
import { StringBuilder } from '../../common/core/stringBuilder.js';
import { ModelLineProjectionData } from '../../common/modelLineProjectionData.js';
import { LineInjectedText } from '../../common/textModelEvents.js';
const ttPolicy = createTrustedTypesPolicy('domLineBreaksComputer', { createHTML: value => value });
export class DOMLineBreaksComputerFactory {
static create(targetWindow) {
return new DOMLineBreaksComputerFactory(new WeakRef(targetWindow));
}
constructor(targetWindow) {
this.targetWindow = targetWindow;
}
createLineBreaksComputer(fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak) {
const requests = [];
const injectedTexts = [];
return {
addRequest: (lineText, injectedText, previousLineBreakData) => {
requests.push(lineText);
injectedTexts.push(injectedText);
},
finalize: () => {
return createLineBreaks(assertIsDefined(this.targetWindow.deref()), requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts);
}
};
}
}
function createLineBreaks(targetWindow, requests, fontInfo, tabSize, firstLineBreakColumn, wrappingIndent, wordBreak, injectedTextsPerLine) {
function createEmptyLineBreakWithPossiblyInjectedText(requestIdx) {
const injectedTexts = injectedTextsPerLine[requestIdx];
if (injectedTexts) {
const lineText = LineInjectedText.applyInjectedText(requests[requestIdx], injectedTexts);
const injectionOptions = injectedTexts.map(t => t.options);
const injectionOffsets = injectedTexts.map(text => text.column - 1);
// creating a `LineBreakData` with an invalid `breakOffsetsVisibleColumn` is OK
// because `breakOffsetsVisibleColumn` will never be used because it contains injected text
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
}
else {
return null;
}
}
if (firstLineBreakColumn === -1) {
const result = [];
for (let i = 0, len = requests.length; i < len; i++) {
result[i] = createEmptyLineBreakWithPossiblyInjectedText(i);
}
return result;
}
const overallWidth = Math.round(firstLineBreakColumn * fontInfo.typicalHalfwidthCharacterWidth);
const additionalIndent = (wrappingIndent === 3 /* WrappingIndent.DeepIndent */ ? 2 : wrappingIndent === 2 /* WrappingIndent.Indent */ ? 1 : 0);
const additionalIndentSize = Math.round(tabSize * additionalIndent);
const additionalIndentLength = Math.ceil(fontInfo.spaceWidth * additionalIndentSize);
const containerDomNode = document.createElement('div');
applyFontInfo(containerDomNode, fontInfo);
const sb = new StringBuilder(10000);
const firstNonWhitespaceIndices = [];
const wrappedTextIndentLengths = [];
const renderLineContents = [];
const allCharOffsets = [];
const allVisibleColumns = [];
for (let i = 0; i < requests.length; i++) {
const lineContent = LineInjectedText.applyInjectedText(requests[i], injectedTextsPerLine[i]);
let firstNonWhitespaceIndex = 0;
let wrappedTextIndentLength = 0;
let width = overallWidth;
if (wrappingIndent !== 0 /* WrappingIndent.None */) {
firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
if (firstNonWhitespaceIndex === -1) {
// all whitespace line
firstNonWhitespaceIndex = 0;
}
else {
// Track existing indent
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
const charWidth = (lineContent.charCodeAt(i) === 9 /* CharCode.Tab */
? (tabSize - (wrappedTextIndentLength % tabSize))
: 1);
wrappedTextIndentLength += charWidth;
}
const indentWidth = Math.ceil(fontInfo.spaceWidth * wrappedTextIndentLength);
// Force sticking to beginning of line if no character would fit except for the indentation
if (indentWidth + fontInfo.typicalFullwidthCharacterWidth > overallWidth) {
firstNonWhitespaceIndex = 0;
wrappedTextIndentLength = 0;
}
else {
width = overallWidth - indentWidth;
}
}
}
const renderLineContent = lineContent.substr(firstNonWhitespaceIndex);
const tmp = renderLine(renderLineContent, wrappedTextIndentLength, tabSize, width, sb, additionalIndentLength);
firstNonWhitespaceIndices[i] = firstNonWhitespaceIndex;
wrappedTextIndentLengths[i] = wrappedTextIndentLength;
renderLineContents[i] = renderLineContent;
allCharOffsets[i] = tmp[0];
allVisibleColumns[i] = tmp[1];
}
const html = sb.build();
const trustedhtml = ttPolicy?.createHTML(html) ?? html;
containerDomNode.innerHTML = trustedhtml;
containerDomNode.style.position = 'absolute';
containerDomNode.style.top = '10000';
if (wordBreak === 'keepAll') {
// word-break: keep-all; overflow-wrap: anywhere
containerDomNode.style.wordBreak = 'keep-all';
containerDomNode.style.overflowWrap = 'anywhere';
}
else {
// overflow-wrap: break-word
containerDomNode.style.wordBreak = 'inherit';
containerDomNode.style.overflowWrap = 'break-word';
}
targetWindow.document.body.appendChild(containerDomNode);
const range = document.createRange();
const lineDomNodes = Array.prototype.slice.call(containerDomNode.children, 0);
const result = [];
for (let i = 0; i < requests.length; i++) {
const lineDomNode = lineDomNodes[i];
const breakOffsets = readLineBreaks(range, lineDomNode, renderLineContents[i], allCharOffsets[i]);
if (breakOffsets === null) {
result[i] = createEmptyLineBreakWithPossiblyInjectedText(i);
continue;
}
const firstNonWhitespaceIndex = firstNonWhitespaceIndices[i];
const wrappedTextIndentLength = wrappedTextIndentLengths[i] + additionalIndentSize;
const visibleColumns = allVisibleColumns[i];
const breakOffsetsVisibleColumn = [];
for (let j = 0, len = breakOffsets.length; j < len; j++) {
breakOffsetsVisibleColumn[j] = visibleColumns[breakOffsets[j]];
}
if (firstNonWhitespaceIndex !== 0) {
// All break offsets are relative to the renderLineContent, make them absolute again
for (let j = 0, len = breakOffsets.length; j < len; j++) {
breakOffsets[j] += firstNonWhitespaceIndex;
}
}
let injectionOptions;
let injectionOffsets;
const curInjectedTexts = injectedTextsPerLine[i];
if (curInjectedTexts) {
injectionOptions = curInjectedTexts.map(t => t.options);
injectionOffsets = curInjectedTexts.map(text => text.column - 1);
}
else {
injectionOptions = null;
injectionOffsets = null;
}
result[i] = new ModelLineProjectionData(injectionOffsets, injectionOptions, breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength);
}
containerDomNode.remove();
return result;
}
function renderLine(lineContent, initialVisibleColumn, tabSize, width, sb, wrappingIndentLength) {
if (wrappingIndentLength !== 0) {
const hangingOffset = String(wrappingIndentLength);
sb.appendString('<div style="text-indent: -');
sb.appendString(hangingOffset);
sb.appendString('px; padding-left: ');
sb.appendString(hangingOffset);
sb.appendString('px; box-sizing: border-box; width:');
}
else {
sb.appendString('<div style="width:');
}
sb.appendString(String(width));
sb.appendString('px;">');
// if (containsRTL) {
// sb.appendASCIIString('" dir="ltr');
// }
const len = lineContent.length;
let visibleColumn = initialVisibleColumn;
let charOffset = 0;
const charOffsets = [];
const visibleColumns = [];
let nextCharCode = (0 < len ? lineContent.charCodeAt(0) : 0 /* CharCode.Null */);
sb.appendString('<span>');
for (let charIndex = 0; charIndex < len; charIndex++) {
if (charIndex !== 0 && charIndex % 16384 /* Constants.SPAN_MODULO_LIMIT */ === 0) {
sb.appendString('</span><span>');
}
charOffsets[charIndex] = charOffset;
visibleColumns[charIndex] = visibleColumn;
const charCode = nextCharCode;
nextCharCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : 0 /* CharCode.Null */);
let producedCharacters = 1;
let charWidth = 1;
switch (charCode) {
case 9 /* CharCode.Tab */:
producedCharacters = (tabSize - (visibleColumn % tabSize));
charWidth = producedCharacters;
for (let space = 1; space <= producedCharacters; space++) {
if (space < producedCharacters) {
sb.appendCharCode(0xA0); // &nbsp;
}
else {
sb.appendASCIICharCode(32 /* CharCode.Space */);
}
}
break;
case 32 /* CharCode.Space */:
if (nextCharCode === 32 /* CharCode.Space */) {
sb.appendCharCode(0xA0); // &nbsp;
}
else {
sb.appendASCIICharCode(32 /* CharCode.Space */);
}
break;
case 60 /* CharCode.LessThan */:
sb.appendString('&lt;');
break;
case 62 /* CharCode.GreaterThan */:
sb.appendString('&gt;');
break;
case 38 /* CharCode.Ampersand */:
sb.appendString('&amp;');
break;
case 0 /* CharCode.Null */:
sb.appendString('&#00;');
break;
case 65279 /* CharCode.UTF8_BOM */:
case 8232 /* CharCode.LINE_SEPARATOR */:
case 8233 /* CharCode.PARAGRAPH_SEPARATOR */:
case 133 /* CharCode.NEXT_LINE */:
sb.appendCharCode(0xFFFD);
break;
default:
if (strings.isFullWidthCharacter(charCode)) {
charWidth++;
}
if (charCode < 32) {
sb.appendCharCode(9216 + charCode);
}
else {
sb.appendCharCode(charCode);
}
}
charOffset += producedCharacters;
visibleColumn += charWidth;
}
sb.appendString('</span>');
charOffsets[lineContent.length] = charOffset;
visibleColumns[lineContent.length] = visibleColumn;
sb.appendString('</div>');
return [charOffsets, visibleColumns];
}
function readLineBreaks(range, lineDomNode, lineContent, charOffsets) {
if (lineContent.length <= 1) {
return null;
}
const spans = Array.prototype.slice.call(lineDomNode.children, 0);
const breakOffsets = [];
try {
discoverBreaks(range, spans, charOffsets, 0, null, lineContent.length - 1, null, breakOffsets);
}
catch (err) {
console.log(err);
return null;
}
if (breakOffsets.length === 0) {
return null;
}
breakOffsets.push(lineContent.length);
return breakOffsets;
}
function discoverBreaks(range, spans, charOffsets, low, lowRects, high, highRects, result) {
if (low === high) {
return;
}
lowRects = lowRects || readClientRect(range, spans, charOffsets[low], charOffsets[low + 1]);
highRects = highRects || readClientRect(range, spans, charOffsets[high], charOffsets[high + 1]);
if (Math.abs(lowRects[0].top - highRects[0].top) <= 0.1) {
// same line
return;
}
// there is at least one line break between these two offsets
if (low + 1 === high) {
// the two characters are adjacent, so the line break must be exactly between them
result.push(high);
return;
}
const mid = low + ((high - low) / 2) | 0;
const midRects = readClientRect(range, spans, charOffsets[mid], charOffsets[mid + 1]);
discoverBreaks(range, spans, charOffsets, low, lowRects, mid, midRects, result);
discoverBreaks(range, spans, charOffsets, mid, midRects, high, highRects, result);
}
function readClientRect(range, spans, startOffset, endOffset) {
range.setStart(spans[(startOffset / 16384 /* Constants.SPAN_MODULO_LIMIT */) | 0].firstChild, startOffset % 16384 /* Constants.SPAN_MODULO_LIMIT */);
range.setEnd(spans[(endOffset / 16384 /* Constants.SPAN_MODULO_LIMIT */) | 0].firstChild, endOffset % 16384 /* Constants.SPAN_MODULO_LIMIT */);
return range.getClientRects();
}
@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ViewEventHandler } from '../../common/viewEventHandler.js';
export class DynamicViewOverlay extends ViewEventHandler {
}
@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class RestrictedRenderingContext {
constructor(viewLayout, viewportData) {
this._restrictedRenderingContextBrand = undefined;
this._viewLayout = viewLayout;
this.viewportData = viewportData;
this.scrollWidth = this._viewLayout.getScrollWidth();
this.scrollHeight = this._viewLayout.getScrollHeight();
this.visibleRange = this.viewportData.visibleRange;
this.bigNumbersDelta = this.viewportData.bigNumbersDelta;
const vInfo = this._viewLayout.getCurrentViewport();
this.scrollTop = vInfo.top;
this.scrollLeft = vInfo.left;
this.viewportWidth = vInfo.width;
this.viewportHeight = vInfo.height;
}
getScrolledTopFromAbsoluteTop(absoluteTop) {
return absoluteTop - this.scrollTop;
}
getVerticalOffsetForLineNumber(lineNumber, includeViewZones) {
return this._viewLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones);
}
getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones) {
return this._viewLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones);
}
getDecorationsInViewport() {
return this.viewportData.getDecorationsInViewport();
}
}
export class RenderingContext extends RestrictedRenderingContext {
constructor(viewLayout, viewportData, viewLines) {
super(viewLayout, viewportData);
this._renderingContextBrand = undefined;
this._viewLines = viewLines;
}
linesVisibleRangesForRange(range, includeNewLines) {
return this._viewLines.linesVisibleRangesForRange(range, includeNewLines);
}
visibleRangeForPosition(position) {
return this._viewLines.visibleRangeForPosition(position);
}
}
export class LineVisibleRanges {
constructor(outsideRenderedLine, lineNumber, ranges,
/**
* Indicates if the requested range does not end in this line, but continues on the next line.
*/
continuesOnNextLine) {
this.outsideRenderedLine = outsideRenderedLine;
this.lineNumber = lineNumber;
this.ranges = ranges;
this.continuesOnNextLine = continuesOnNextLine;
}
}
export class HorizontalRange {
static from(ranges) {
const result = new Array(ranges.length);
for (let i = 0, len = ranges.length; i < len; i++) {
const range = ranges[i];
result[i] = new HorizontalRange(range.left, range.width);
}
return result;
}
constructor(left, width) {
this._horizontalRangeBrand = undefined;
this.left = Math.round(left);
this.width = Math.round(width);
}
toString() {
return `[${this.left},${this.width}]`;
}
}
export class FloatHorizontalRange {
constructor(left, width) {
this._floatHorizontalRangeBrand = undefined;
this.left = left;
this.width = width;
}
toString() {
return `[${this.left},${this.width}]`;
}
static compare(a, b) {
return a.left - b.left;
}
}
export class HorizontalPosition {
constructor(outsideRenderedLine, left) {
this.outsideRenderedLine = outsideRenderedLine;
this.originalLeft = left;
this.left = Math.round(this.originalLeft);
}
}
export class VisibleRanges {
constructor(outsideRenderedLine, ranges) {
this.outsideRenderedLine = outsideRenderedLine;
this.ranges = ranges;
}
}
@@ -0,0 +1,267 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CoreNavigationCommands } from '../coreCommands.js';
import { Position } from '../../common/core/position.js';
import * as platform from '../../../base/common/platform.js';
export class ViewController {
constructor(configuration, viewModel, userInputEvents, commandDelegate) {
this.configuration = configuration;
this.viewModel = viewModel;
this.userInputEvents = userInputEvents;
this.commandDelegate = commandDelegate;
}
paste(text, pasteOnNewLine, multicursorText, mode) {
this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode);
}
type(text) {
this.commandDelegate.type(text);
}
compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta) {
this.commandDelegate.compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta);
}
compositionStart() {
this.commandDelegate.startComposition();
}
compositionEnd() {
this.commandDelegate.endComposition();
}
cut() {
this.commandDelegate.cut();
}
setSelection(modelSelection) {
CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, {
source: 'keyboard',
selection: modelSelection
});
}
_validateViewColumn(viewPosition) {
const minColumn = this.viewModel.getLineMinColumn(viewPosition.lineNumber);
if (viewPosition.column < minColumn) {
return new Position(viewPosition.lineNumber, minColumn);
}
return viewPosition;
}
_hasMulticursorModifier(data) {
switch (this.configuration.options.get(78 /* EditorOption.multiCursorModifier */)) {
case 'altKey':
return data.altKey;
case 'ctrlKey':
return data.ctrlKey;
case 'metaKey':
return data.metaKey;
default:
return false;
}
}
_hasNonMulticursorModifier(data) {
switch (this.configuration.options.get(78 /* EditorOption.multiCursorModifier */)) {
case 'altKey':
return data.ctrlKey || data.metaKey;
case 'ctrlKey':
return data.altKey || data.metaKey;
case 'metaKey':
return data.ctrlKey || data.altKey;
default:
return false;
}
}
dispatchMouse(data) {
const options = this.configuration.options;
const selectionClipboardIsOn = (platform.isLinux && options.get(108 /* EditorOption.selectionClipboard */));
const columnSelection = options.get(22 /* EditorOption.columnSelection */);
if (data.middleButton && !selectionClipboardIsOn) {
this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode);
}
else if (data.startedOnLineNumbers) {
// If the dragging started on the gutter, then have operations work on the entire line
if (this._hasMulticursorModifier(data)) {
if (data.inSelectionMode) {
this._lastCursorLineSelect(data.position, data.revealType);
}
else {
this._createCursor(data.position, true);
}
}
else {
if (data.inSelectionMode) {
this._lineSelectDrag(data.position, data.revealType);
}
else {
this._lineSelect(data.position, data.revealType);
}
}
}
else if (data.mouseDownCount >= 4) {
this._selectAll();
}
else if (data.mouseDownCount === 3) {
if (this._hasMulticursorModifier(data)) {
if (data.inSelectionMode) {
this._lastCursorLineSelectDrag(data.position, data.revealType);
}
else {
this._lastCursorLineSelect(data.position, data.revealType);
}
}
else {
if (data.inSelectionMode) {
this._lineSelectDrag(data.position, data.revealType);
}
else {
this._lineSelect(data.position, data.revealType);
}
}
}
else if (data.mouseDownCount === 2) {
if (!data.onInjectedText) {
if (this._hasMulticursorModifier(data)) {
this._lastCursorWordSelect(data.position, data.revealType);
}
else {
if (data.inSelectionMode) {
this._wordSelectDrag(data.position, data.revealType);
}
else {
this._wordSelect(data.position, data.revealType);
}
}
}
}
else {
if (this._hasMulticursorModifier(data)) {
if (!this._hasNonMulticursorModifier(data)) {
if (data.shiftKey) {
this._columnSelect(data.position, data.mouseColumn, true);
}
else {
// Do multi-cursor operations only when purely alt is pressed
if (data.inSelectionMode) {
this._lastCursorMoveToSelect(data.position, data.revealType);
}
else {
this._createCursor(data.position, false);
}
}
}
}
else {
if (data.inSelectionMode) {
if (data.altKey) {
this._columnSelect(data.position, data.mouseColumn, true);
}
else {
if (columnSelection) {
this._columnSelect(data.position, data.mouseColumn, true);
}
else {
this._moveToSelect(data.position, data.revealType);
}
}
}
else {
this.moveTo(data.position, data.revealType);
}
}
}
}
_usualArgs(viewPosition, revealType) {
viewPosition = this._validateViewColumn(viewPosition);
return {
source: 'mouse',
position: this._convertViewToModelPosition(viewPosition),
viewPosition,
revealType
};
}
moveTo(viewPosition, revealType) {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_moveToSelect(viewPosition, revealType) {
CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_columnSelect(viewPosition, mouseColumn, doColumnSelect) {
viewPosition = this._validateViewColumn(viewPosition);
CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(this.viewModel, {
source: 'mouse',
position: this._convertViewToModelPosition(viewPosition),
viewPosition: viewPosition,
mouseColumn: mouseColumn,
doColumnSelect: doColumnSelect
});
}
_createCursor(viewPosition, wholeLine) {
viewPosition = this._validateViewColumn(viewPosition);
CoreNavigationCommands.CreateCursor.runCoreEditorCommand(this.viewModel, {
source: 'mouse',
position: this._convertViewToModelPosition(viewPosition),
viewPosition: viewPosition,
wholeLine: wholeLine
});
}
_lastCursorMoveToSelect(viewPosition, revealType) {
CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_wordSelect(viewPosition, revealType) {
CoreNavigationCommands.WordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_wordSelectDrag(viewPosition, revealType) {
CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_lastCursorWordSelect(viewPosition, revealType) {
CoreNavigationCommands.LastCursorWordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_lineSelect(viewPosition, revealType) {
CoreNavigationCommands.LineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_lineSelectDrag(viewPosition, revealType) {
CoreNavigationCommands.LineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_lastCursorLineSelect(viewPosition, revealType) {
CoreNavigationCommands.LastCursorLineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_lastCursorLineSelectDrag(viewPosition, revealType) {
CoreNavigationCommands.LastCursorLineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
}
_selectAll() {
CoreNavigationCommands.SelectAll.runCoreEditorCommand(this.viewModel, { source: 'mouse' });
}
// ----------------------
_convertViewToModelPosition(viewPosition) {
return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(viewPosition);
}
emitKeyDown(e) {
this.userInputEvents.emitKeyDown(e);
}
emitKeyUp(e) {
this.userInputEvents.emitKeyUp(e);
}
emitContextMenu(e) {
this.userInputEvents.emitContextMenu(e);
}
emitMouseMove(e) {
this.userInputEvents.emitMouseMove(e);
}
emitMouseLeave(e) {
this.userInputEvents.emitMouseLeave(e);
}
emitMouseUp(e) {
this.userInputEvents.emitMouseUp(e);
}
emitMouseDown(e) {
this.userInputEvents.emitMouseDown(e);
}
emitMouseDrag(e) {
this.userInputEvents.emitMouseDrag(e);
}
emitMouseDrop(e) {
this.userInputEvents.emitMouseDrop(e);
}
emitMouseDropCanceled() {
this.userInputEvents.emitMouseDropCanceled();
}
emitMouseWheel(e) {
this.userInputEvents.emitMouseWheel(e);
}
}
@@ -0,0 +1,457 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../base/browser/fastDomNode.js';
import { createTrustedTypesPolicy } from '../../../base/browser/trustedTypes.js';
import { BugIndicatingError } from '../../../base/common/errors.js';
import { StringBuilder } from '../../common/core/stringBuilder.js';
export class RenderedLinesCollection {
constructor(createLine) {
this._createLine = createLine;
this._set(1, []);
}
flush() {
this._set(1, []);
}
_set(rendLineNumberStart, lines) {
this._lines = lines;
this._rendLineNumberStart = rendLineNumberStart;
}
_get() {
return {
rendLineNumberStart: this._rendLineNumberStart,
lines: this._lines
};
}
/**
* @returns Inclusive line number that is inside this collection
*/
getStartLineNumber() {
return this._rendLineNumberStart;
}
/**
* @returns Inclusive line number that is inside this collection
*/
getEndLineNumber() {
return this._rendLineNumberStart + this._lines.length - 1;
}
getCount() {
return this._lines.length;
}
getLine(lineNumber) {
const lineIndex = lineNumber - this._rendLineNumberStart;
if (lineIndex < 0 || lineIndex >= this._lines.length) {
throw new BugIndicatingError('Illegal value for lineNumber');
}
return this._lines[lineIndex];
}
/**
* @returns Lines that were removed from this collection
*/
onLinesDeleted(deleteFromLineNumber, deleteToLineNumber) {
if (this.getCount() === 0) {
// no lines
return null;
}
const startLineNumber = this.getStartLineNumber();
const endLineNumber = this.getEndLineNumber();
if (deleteToLineNumber < startLineNumber) {
// deleting above the viewport
const deleteCnt = deleteToLineNumber - deleteFromLineNumber + 1;
this._rendLineNumberStart -= deleteCnt;
return null;
}
if (deleteFromLineNumber > endLineNumber) {
// deleted below the viewport
return null;
}
// Record what needs to be deleted
let deleteStartIndex = 0;
let deleteCount = 0;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const lineIndex = lineNumber - this._rendLineNumberStart;
if (deleteFromLineNumber <= lineNumber && lineNumber <= deleteToLineNumber) {
// this is a line to be deleted
if (deleteCount === 0) {
// this is the first line to be deleted
deleteStartIndex = lineIndex;
deleteCount = 1;
}
else {
deleteCount++;
}
}
}
// Adjust this._rendLineNumberStart for lines deleted above
if (deleteFromLineNumber < startLineNumber) {
// Something was deleted above
let deleteAboveCount = 0;
if (deleteToLineNumber < startLineNumber) {
// the entire deleted lines are above
deleteAboveCount = deleteToLineNumber - deleteFromLineNumber + 1;
}
else {
deleteAboveCount = startLineNumber - deleteFromLineNumber;
}
this._rendLineNumberStart -= deleteAboveCount;
}
const deleted = this._lines.splice(deleteStartIndex, deleteCount);
return deleted;
}
onLinesChanged(changeFromLineNumber, changeCount) {
const changeToLineNumber = changeFromLineNumber + changeCount - 1;
if (this.getCount() === 0) {
// no lines
return false;
}
const startLineNumber = this.getStartLineNumber();
const endLineNumber = this.getEndLineNumber();
let someoneNotified = false;
for (let changedLineNumber = changeFromLineNumber; changedLineNumber <= changeToLineNumber; changedLineNumber++) {
if (changedLineNumber >= startLineNumber && changedLineNumber <= endLineNumber) {
// Notify the line
this._lines[changedLineNumber - this._rendLineNumberStart].onContentChanged();
someoneNotified = true;
}
}
return someoneNotified;
}
onLinesInserted(insertFromLineNumber, insertToLineNumber) {
if (this.getCount() === 0) {
// no lines
return null;
}
const insertCnt = insertToLineNumber - insertFromLineNumber + 1;
const startLineNumber = this.getStartLineNumber();
const endLineNumber = this.getEndLineNumber();
if (insertFromLineNumber <= startLineNumber) {
// inserting above the viewport
this._rendLineNumberStart += insertCnt;
return null;
}
if (insertFromLineNumber > endLineNumber) {
// inserting below the viewport
return null;
}
if (insertCnt + insertFromLineNumber > endLineNumber) {
// insert inside the viewport in such a way that all remaining lines are pushed outside
const deleted = this._lines.splice(insertFromLineNumber - this._rendLineNumberStart, endLineNumber - insertFromLineNumber + 1);
return deleted;
}
// insert inside the viewport, push out some lines, but not all remaining lines
const newLines = [];
for (let i = 0; i < insertCnt; i++) {
newLines[i] = this._createLine();
}
const insertIndex = insertFromLineNumber - this._rendLineNumberStart;
const beforeLines = this._lines.slice(0, insertIndex);
const afterLines = this._lines.slice(insertIndex, this._lines.length - insertCnt);
const deletedLines = this._lines.slice(this._lines.length - insertCnt, this._lines.length);
this._lines = beforeLines.concat(newLines).concat(afterLines);
return deletedLines;
}
onTokensChanged(ranges) {
if (this.getCount() === 0) {
// no lines
return false;
}
const startLineNumber = this.getStartLineNumber();
const endLineNumber = this.getEndLineNumber();
let notifiedSomeone = false;
for (let i = 0, len = ranges.length; i < len; i++) {
const rng = ranges[i];
if (rng.toLineNumber < startLineNumber || rng.fromLineNumber > endLineNumber) {
// range outside viewport
continue;
}
const from = Math.max(startLineNumber, rng.fromLineNumber);
const to = Math.min(endLineNumber, rng.toLineNumber);
for (let lineNumber = from; lineNumber <= to; lineNumber++) {
const lineIndex = lineNumber - this._rendLineNumberStart;
this._lines[lineIndex].onTokensChanged();
notifiedSomeone = true;
}
}
return notifiedSomeone;
}
}
export class VisibleLinesCollection {
constructor(host) {
this._host = host;
this.domNode = this._createDomNode();
this._linesCollection = new RenderedLinesCollection(() => this._host.createVisibleLine());
}
_createDomNode() {
const domNode = createFastDomNode(document.createElement('div'));
domNode.setClassName('view-layer');
domNode.setPosition('absolute');
domNode.domNode.setAttribute('role', 'presentation');
domNode.domNode.setAttribute('aria-hidden', 'true');
return domNode;
}
// ---- begin view event handlers
onConfigurationChanged(e) {
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
return true;
}
return false;
}
onFlushed(e) {
this._linesCollection.flush();
// No need to clear the dom node because a full .innerHTML will occur in ViewLayerRenderer._render
return true;
}
onLinesChanged(e) {
return this._linesCollection.onLinesChanged(e.fromLineNumber, e.count);
}
onLinesDeleted(e) {
const deleted = this._linesCollection.onLinesDeleted(e.fromLineNumber, e.toLineNumber);
if (deleted) {
// Remove from DOM
for (let i = 0, len = deleted.length; i < len; i++) {
const lineDomNode = deleted[i].getDomNode();
lineDomNode?.remove();
}
}
return true;
}
onLinesInserted(e) {
const deleted = this._linesCollection.onLinesInserted(e.fromLineNumber, e.toLineNumber);
if (deleted) {
// Remove from DOM
for (let i = 0, len = deleted.length; i < len; i++) {
const lineDomNode = deleted[i].getDomNode();
lineDomNode?.remove();
}
}
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onTokensChanged(e) {
return this._linesCollection.onTokensChanged(e.ranges);
}
onZonesChanged(e) {
return true;
}
// ---- end view event handlers
getStartLineNumber() {
return this._linesCollection.getStartLineNumber();
}
getEndLineNumber() {
return this._linesCollection.getEndLineNumber();
}
getVisibleLine(lineNumber) {
return this._linesCollection.getLine(lineNumber);
}
renderLines(viewportData) {
const inp = this._linesCollection._get();
const renderer = new ViewLayerRenderer(this.domNode.domNode, this._host, viewportData);
const ctx = {
rendLineNumberStart: inp.rendLineNumberStart,
lines: inp.lines,
linesLength: inp.lines.length
};
// Decide if this render will do a single update (single large .innerHTML) or many updates (inserting/removing dom nodes)
const resCtx = renderer.render(ctx, viewportData.startLineNumber, viewportData.endLineNumber, viewportData.relativeVerticalOffset);
this._linesCollection._set(resCtx.rendLineNumberStart, resCtx.lines);
}
}
class ViewLayerRenderer {
static { this._ttPolicy = createTrustedTypesPolicy('editorViewLayer', { createHTML: value => value }); }
constructor(domNode, host, viewportData) {
this.domNode = domNode;
this.host = host;
this.viewportData = viewportData;
}
render(inContext, startLineNumber, stopLineNumber, deltaTop) {
const ctx = {
rendLineNumberStart: inContext.rendLineNumberStart,
lines: inContext.lines.slice(0),
linesLength: inContext.linesLength
};
if ((ctx.rendLineNumberStart + ctx.linesLength - 1 < startLineNumber) || (stopLineNumber < ctx.rendLineNumberStart)) {
// There is no overlap whatsoever
ctx.rendLineNumberStart = startLineNumber;
ctx.linesLength = stopLineNumber - startLineNumber + 1;
ctx.lines = [];
for (let x = startLineNumber; x <= stopLineNumber; x++) {
ctx.lines[x - startLineNumber] = this.host.createVisibleLine();
}
this._finishRendering(ctx, true, deltaTop);
return ctx;
}
// Update lines which will remain untouched
this._renderUntouchedLines(ctx, Math.max(startLineNumber - ctx.rendLineNumberStart, 0), Math.min(stopLineNumber - ctx.rendLineNumberStart, ctx.linesLength - 1), deltaTop, startLineNumber);
if (ctx.rendLineNumberStart > startLineNumber) {
// Insert lines before
const fromLineNumber = startLineNumber;
const toLineNumber = Math.min(stopLineNumber, ctx.rendLineNumberStart - 1);
if (fromLineNumber <= toLineNumber) {
this._insertLinesBefore(ctx, fromLineNumber, toLineNumber, deltaTop, startLineNumber);
ctx.linesLength += toLineNumber - fromLineNumber + 1;
}
}
else if (ctx.rendLineNumberStart < startLineNumber) {
// Remove lines before
const removeCnt = Math.min(ctx.linesLength, startLineNumber - ctx.rendLineNumberStart);
if (removeCnt > 0) {
this._removeLinesBefore(ctx, removeCnt);
ctx.linesLength -= removeCnt;
}
}
ctx.rendLineNumberStart = startLineNumber;
if (ctx.rendLineNumberStart + ctx.linesLength - 1 < stopLineNumber) {
// Insert lines after
const fromLineNumber = ctx.rendLineNumberStart + ctx.linesLength;
const toLineNumber = stopLineNumber;
if (fromLineNumber <= toLineNumber) {
this._insertLinesAfter(ctx, fromLineNumber, toLineNumber, deltaTop, startLineNumber);
ctx.linesLength += toLineNumber - fromLineNumber + 1;
}
}
else if (ctx.rendLineNumberStart + ctx.linesLength - 1 > stopLineNumber) {
// Remove lines after
const fromLineNumber = Math.max(0, stopLineNumber - ctx.rendLineNumberStart + 1);
const toLineNumber = ctx.linesLength - 1;
const removeCnt = toLineNumber - fromLineNumber + 1;
if (removeCnt > 0) {
this._removeLinesAfter(ctx, removeCnt);
ctx.linesLength -= removeCnt;
}
}
this._finishRendering(ctx, false, deltaTop);
return ctx;
}
_renderUntouchedLines(ctx, startIndex, endIndex, deltaTop, deltaLN) {
const rendLineNumberStart = ctx.rendLineNumberStart;
const lines = ctx.lines;
for (let i = startIndex; i <= endIndex; i++) {
const lineNumber = rendLineNumberStart + i;
lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN], this.viewportData.lineHeight);
}
}
_insertLinesBefore(ctx, fromLineNumber, toLineNumber, deltaTop, deltaLN) {
const newLines = [];
let newLinesLen = 0;
for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
newLines[newLinesLen++] = this.host.createVisibleLine();
}
ctx.lines = newLines.concat(ctx.lines);
}
_removeLinesBefore(ctx, removeCount) {
for (let i = 0; i < removeCount; i++) {
const lineDomNode = ctx.lines[i].getDomNode();
lineDomNode?.remove();
}
ctx.lines.splice(0, removeCount);
}
_insertLinesAfter(ctx, fromLineNumber, toLineNumber, deltaTop, deltaLN) {
const newLines = [];
let newLinesLen = 0;
for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
newLines[newLinesLen++] = this.host.createVisibleLine();
}
ctx.lines = ctx.lines.concat(newLines);
}
_removeLinesAfter(ctx, removeCount) {
const removeIndex = ctx.linesLength - removeCount;
for (let i = 0; i < removeCount; i++) {
const lineDomNode = ctx.lines[removeIndex + i].getDomNode();
lineDomNode?.remove();
}
ctx.lines.splice(removeIndex, removeCount);
}
_finishRenderingNewLines(ctx, domNodeIsEmpty, newLinesHTML, wasNew) {
if (ViewLayerRenderer._ttPolicy) {
newLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(newLinesHTML);
}
const lastChild = this.domNode.lastChild;
if (domNodeIsEmpty || !lastChild) {
this.domNode.innerHTML = newLinesHTML; // explains the ugly casts -> https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393;
}
else {
lastChild.insertAdjacentHTML('afterend', newLinesHTML);
}
let currChild = this.domNode.lastChild;
for (let i = ctx.linesLength - 1; i >= 0; i--) {
const line = ctx.lines[i];
if (wasNew[i]) {
line.setDomNode(currChild);
currChild = currChild.previousSibling;
}
}
}
_finishRenderingInvalidLines(ctx, invalidLinesHTML, wasInvalid) {
const hugeDomNode = document.createElement('div');
if (ViewLayerRenderer._ttPolicy) {
invalidLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(invalidLinesHTML);
}
hugeDomNode.innerHTML = invalidLinesHTML;
for (let i = 0; i < ctx.linesLength; i++) {
const line = ctx.lines[i];
if (wasInvalid[i]) {
const source = hugeDomNode.firstChild;
const lineDomNode = line.getDomNode();
lineDomNode.parentNode.replaceChild(source, lineDomNode);
line.setDomNode(source);
}
}
}
static { this._sb = new StringBuilder(100000); }
_finishRendering(ctx, domNodeIsEmpty, deltaTop) {
const sb = ViewLayerRenderer._sb;
const linesLength = ctx.linesLength;
const lines = ctx.lines;
const rendLineNumberStart = ctx.rendLineNumberStart;
const wasNew = [];
{
sb.reset();
let hadNewLine = false;
for (let i = 0; i < linesLength; i++) {
const line = lines[i];
wasNew[i] = false;
const lineDomNode = line.getDomNode();
if (lineDomNode) {
// line is not new
continue;
}
const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb);
if (!renderResult) {
// line does not need rendering
continue;
}
wasNew[i] = true;
hadNewLine = true;
}
if (hadNewLine) {
this._finishRenderingNewLines(ctx, domNodeIsEmpty, sb.build(), wasNew);
}
}
{
sb.reset();
let hadInvalidLine = false;
const wasInvalid = [];
for (let i = 0; i < linesLength; i++) {
const line = lines[i];
wasInvalid[i] = false;
if (wasNew[i]) {
// line was new
continue;
}
const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb);
if (!renderResult) {
// line does not need rendering
continue;
}
wasInvalid[i] = true;
hadInvalidLine = true;
}
if (hadInvalidLine) {
this._finishRenderingInvalidLines(ctx, sb.build(), wasInvalid);
}
}
}
}
@@ -0,0 +1,201 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../base/browser/fastDomNode.js';
import { applyFontInfo } from '../config/domFontInfo.js';
import { VisibleLinesCollection } from './viewLayer.js';
import { ViewPart } from './viewPart.js';
export class ViewOverlays extends ViewPart {
constructor(context) {
super(context);
this._visibleLines = new VisibleLinesCollection(this);
this.domNode = this._visibleLines.domNode;
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
applyFontInfo(this.domNode, fontInfo);
this._dynamicOverlays = [];
this._isFocused = false;
this.domNode.setClassName('view-overlays');
}
shouldRender() {
if (super.shouldRender()) {
return true;
}
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
const dynamicOverlay = this._dynamicOverlays[i];
if (dynamicOverlay.shouldRender()) {
return true;
}
}
return false;
}
dispose() {
super.dispose();
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
const dynamicOverlay = this._dynamicOverlays[i];
dynamicOverlay.dispose();
}
this._dynamicOverlays = [];
}
getDomNode() {
return this.domNode;
}
// ---- begin IVisibleLinesHost
createVisibleLine() {
return new ViewOverlayLine(this._dynamicOverlays);
}
// ---- end IVisibleLinesHost
addDynamicOverlay(overlay) {
this._dynamicOverlays.push(overlay);
}
// ----- event handlers
onConfigurationChanged(e) {
this._visibleLines.onConfigurationChanged(e);
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
applyFontInfo(this.domNode, fontInfo);
return true;
}
onFlushed(e) {
return this._visibleLines.onFlushed(e);
}
onFocusChanged(e) {
this._isFocused = e.isFocused;
return true;
}
onLinesChanged(e) {
return this._visibleLines.onLinesChanged(e);
}
onLinesDeleted(e) {
return this._visibleLines.onLinesDeleted(e);
}
onLinesInserted(e) {
return this._visibleLines.onLinesInserted(e);
}
onScrollChanged(e) {
return this._visibleLines.onScrollChanged(e) || true;
}
onTokensChanged(e) {
return this._visibleLines.onTokensChanged(e);
}
onZonesChanged(e) {
return this._visibleLines.onZonesChanged(e);
}
// ----- end event handlers
prepareRender(ctx) {
const toRender = this._dynamicOverlays.filter(overlay => overlay.shouldRender());
for (let i = 0, len = toRender.length; i < len; i++) {
const dynamicOverlay = toRender[i];
dynamicOverlay.prepareRender(ctx);
dynamicOverlay.onDidRender();
}
}
render(ctx) {
// Overwriting to bypass `shouldRender` flag
this._viewOverlaysRender(ctx);
this.domNode.toggleClassName('focused', this._isFocused);
}
_viewOverlaysRender(ctx) {
this._visibleLines.renderLines(ctx.viewportData);
}
}
export class ViewOverlayLine {
constructor(dynamicOverlays) {
this._dynamicOverlays = dynamicOverlays;
this._domNode = null;
this._renderedContent = null;
}
getDomNode() {
if (!this._domNode) {
return null;
}
return this._domNode.domNode;
}
setDomNode(domNode) {
this._domNode = createFastDomNode(domNode);
}
onContentChanged() {
// Nothing
}
onTokensChanged() {
// Nothing
}
renderLine(lineNumber, deltaTop, lineHeight, viewportData, sb) {
let result = '';
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
const dynamicOverlay = this._dynamicOverlays[i];
result += dynamicOverlay.render(viewportData.startLineNumber, lineNumber);
}
if (this._renderedContent === result) {
// No rendering needed
return false;
}
this._renderedContent = result;
sb.appendString('<div style="top:');
sb.appendString(String(deltaTop));
sb.appendString('px;height:');
sb.appendString(String(lineHeight));
sb.appendString('px;">');
sb.appendString(result);
sb.appendString('</div>');
return true;
}
layoutLine(lineNumber, deltaTop, lineHeight) {
if (this._domNode) {
this._domNode.setTop(deltaTop);
this._domNode.setHeight(lineHeight);
}
}
}
export class ContentViewOverlays extends ViewOverlays {
constructor(context) {
super(context);
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._contentWidth = layoutInfo.contentWidth;
this.domNode.setHeight(0);
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._contentWidth = layoutInfo.contentWidth;
return super.onConfigurationChanged(e) || true;
}
onScrollChanged(e) {
return super.onScrollChanged(e) || e.scrollWidthChanged;
}
// --- end event handlers
_viewOverlaysRender(ctx) {
super._viewOverlaysRender(ctx);
this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
}
}
export class MarginViewOverlays extends ViewOverlays {
constructor(context) {
super(context);
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._contentLeft = layoutInfo.contentLeft;
this.domNode.setClassName('margin-view-overlays');
this.domNode.setWidth(1);
applyFontInfo(this.domNode, options.get(50 /* EditorOption.fontInfo */));
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
applyFontInfo(this.domNode, options.get(50 /* EditorOption.fontInfo */));
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._contentLeft = layoutInfo.contentLeft;
return super.onConfigurationChanged(e) || true;
}
onScrollChanged(e) {
return super.onScrollChanged(e) || e.scrollHeightChanged;
}
_viewOverlaysRender(ctx) {
super._viewOverlaysRender(ctx);
const height = Math.min(ctx.scrollHeight, 1000000);
this.domNode.setHeight(height);
this.domNode.setWidth(this._contentLeft);
}
}
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ViewEventHandler } from '../../common/viewEventHandler.js';
export class ViewPart extends ViewEventHandler {
constructor(context) {
super();
this._context = context;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
super.dispose();
}
}
export class PartFingerprints {
static write(target, partId) {
target.setAttribute('data-mprt', String(partId));
}
static read(target) {
const r = target.getAttribute('data-mprt');
if (r === null) {
return 0 /* PartFingerprint.None */;
}
return parseInt(r, 10);
}
static collect(child, stopAt) {
const result = [];
let resultLen = 0;
while (child && child !== child.ownerDocument.body) {
if (child === stopAt) {
break;
}
if (child.nodeType === child.ELEMENT_NODE) {
result[resultLen++] = this.read(child);
}
child = child.parentElement;
}
const r = new Uint8Array(resultLen);
for (let i = 0; i < resultLen; i++) {
r[i] = result[resultLen - i - 1];
}
return r;
}
}
@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position } from '../../common/core/position.js';
export class ViewUserInputEvents {
constructor(coordinatesConverter) {
this.onKeyDown = null;
this.onKeyUp = null;
this.onContextMenu = null;
this.onMouseMove = null;
this.onMouseLeave = null;
this.onMouseDown = null;
this.onMouseUp = null;
this.onMouseDrag = null;
this.onMouseDrop = null;
this.onMouseDropCanceled = null;
this.onMouseWheel = null;
this._coordinatesConverter = coordinatesConverter;
}
emitKeyDown(e) {
this.onKeyDown?.(e);
}
emitKeyUp(e) {
this.onKeyUp?.(e);
}
emitContextMenu(e) {
this.onContextMenu?.(this._convertViewToModelMouseEvent(e));
}
emitMouseMove(e) {
this.onMouseMove?.(this._convertViewToModelMouseEvent(e));
}
emitMouseLeave(e) {
this.onMouseLeave?.(this._convertViewToModelMouseEvent(e));
}
emitMouseDown(e) {
this.onMouseDown?.(this._convertViewToModelMouseEvent(e));
}
emitMouseUp(e) {
this.onMouseUp?.(this._convertViewToModelMouseEvent(e));
}
emitMouseDrag(e) {
this.onMouseDrag?.(this._convertViewToModelMouseEvent(e));
}
emitMouseDrop(e) {
this.onMouseDrop?.(this._convertViewToModelMouseEvent(e));
}
emitMouseDropCanceled() {
this.onMouseDropCanceled?.();
}
emitMouseWheel(e) {
this.onMouseWheel?.(e);
}
_convertViewToModelMouseEvent(e) {
if (e.target) {
return {
event: e.event,
target: this._convertViewToModelMouseTarget(e.target)
};
}
return e;
}
_convertViewToModelMouseTarget(target) {
return ViewUserInputEvents.convertViewToModelMouseTarget(target, this._coordinatesConverter);
}
static convertViewToModelMouseTarget(target, coordinatesConverter) {
const result = { ...target };
if (result.position) {
result.position = coordinatesConverter.convertViewPositionToModelPosition(result.position);
}
if (result.range) {
result.range = coordinatesConverter.convertViewRangeToModelRange(result.range);
}
if (result.type === 5 /* MouseTargetType.GUTTER_VIEW_ZONE */ || result.type === 8 /* MouseTargetType.CONTENT_VIEW_ZONE */) {
result.detail = this.convertViewToModelViewZoneData(result.detail, coordinatesConverter);
}
return result;
}
static convertViewToModelViewZoneData(data, coordinatesConverter) {
return {
viewZoneId: data.viewZoneId,
positionBefore: data.positionBefore ? coordinatesConverter.convertViewPositionToModelPosition(data.positionBefore) : data.positionBefore,
positionAfter: data.positionAfter ? coordinatesConverter.convertViewPositionToModelPosition(data.positionAfter) : data.positionAfter,
position: coordinatesConverter.convertViewPositionToModelPosition(data.position),
afterLineNumber: coordinatesConverter.convertViewPositionToModelPosition(new Position(data.afterLineNumber, 1)).lineNumber,
};
}
}
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .blockDecorations-container {
position: absolute;
top: 0;
pointer-events: none;
}
.monaco-editor .blockDecorations-block {
position: absolute;
box-sizing: border-box;
}
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import './blockDecorations.css';
import { ViewPart } from '../../view/viewPart.js';
export class BlockDecorations extends ViewPart {
constructor(context) {
super(context);
this.blocks = [];
this.contentWidth = -1;
this.contentLeft = 0;
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.domNode.setClassName('blockDecorations-container');
this.update();
}
update() {
let didChange = false;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const newContentWidth = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth;
if (this.contentWidth !== newContentWidth) {
this.contentWidth = newContentWidth;
didChange = true;
}
const newContentLeft = layoutInfo.contentLeft;
if (this.contentLeft !== newContentLeft) {
this.contentLeft = newContentLeft;
didChange = true;
}
return didChange;
}
dispose() {
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
return this.update();
}
onScrollChanged(e) {
return e.scrollTopChanged || e.scrollLeftChanged;
}
onDecorationsChanged(e) {
return true;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
prepareRender(ctx) {
// Nothing to read
}
render(ctx) {
let count = 0;
const decorations = ctx.getDecorationsInViewport();
for (const decoration of decorations) {
if (!decoration.options.blockClassName) {
continue;
}
let block = this.blocks[count];
if (!block) {
block = this.blocks[count] = createFastDomNode(document.createElement('div'));
this.domNode.appendChild(block);
}
let top;
let bottom;
if (decoration.options.blockIsAfterEnd) {
// range must be empty
top = ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, false);
bottom = ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, true);
}
else {
top = ctx.getVerticalOffsetForLineNumber(decoration.range.startLineNumber, true);
bottom = decoration.range.isEmpty() && !decoration.options.blockDoesNotCollapse
? ctx.getVerticalOffsetForLineNumber(decoration.range.startLineNumber, false)
: ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, true);
}
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = decoration.options.blockPadding ?? [0, 0, 0, 0];
block.setClassName('blockDecorations-block ' + decoration.options.blockClassName);
block.setLeft(this.contentLeft - paddingLeft);
block.setWidth(this.contentWidth + paddingLeft + paddingRight);
block.setTop(top - ctx.scrollTop - paddingTop);
block.setHeight(bottom - top + paddingTop + paddingBottom);
count++;
}
for (let i = count; i < this.blocks.length; i++) {
this.blocks[i].domNode.remove();
}
this.blocks.length = count;
}
}
@@ -0,0 +1,486 @@
/*---------------------------------------------------------------------------------------------
* 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 { PartFingerprints, ViewPart } from '../../view/viewPart.js';
export class ViewContentWidgets extends ViewPart {
constructor(context, viewDomNode) {
super(context);
this._viewDomNode = viewDomNode;
this._widgets = {};
this.domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.domNode, 1 /* PartFingerprint.ContentWidgets */);
this.domNode.setClassName('contentWidgets');
this.domNode.setPosition('absolute');
this.domNode.setTop(0);
this.overflowingContentWidgetsDomNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.overflowingContentWidgetsDomNode, 2 /* PartFingerprint.OverflowingContentWidgets */);
this.overflowingContentWidgetsDomNode.setClassName('overflowingContentWidgets');
}
dispose() {
super.dispose();
this._widgets = {};
}
// --- begin event handlers
onConfigurationChanged(e) {
const keys = Object.keys(this._widgets);
for (const widgetId of keys) {
this._widgets[widgetId].onConfigurationChanged(e);
}
return true;
}
onDecorationsChanged(e) {
// true for inline decorations that can end up relayouting text
return true;
}
onFlushed(e) {
return true;
}
onLineMappingChanged(e) {
this._updateAnchorsViewPositions();
return true;
}
onLinesChanged(e) {
this._updateAnchorsViewPositions();
return true;
}
onLinesDeleted(e) {
this._updateAnchorsViewPositions();
return true;
}
onLinesInserted(e) {
this._updateAnchorsViewPositions();
return true;
}
onScrollChanged(e) {
return true;
}
onZonesChanged(e) {
return true;
}
// ---- end view event handlers
_updateAnchorsViewPositions() {
const keys = Object.keys(this._widgets);
for (const widgetId of keys) {
this._widgets[widgetId].updateAnchorViewPosition();
}
}
addWidget(_widget) {
const myWidget = new Widget(this._context, this._viewDomNode, _widget);
this._widgets[myWidget.id] = myWidget;
if (myWidget.allowEditorOverflow) {
this.overflowingContentWidgetsDomNode.appendChild(myWidget.domNode);
}
else {
this.domNode.appendChild(myWidget.domNode);
}
this.setShouldRender();
}
setWidgetPosition(widget, primaryAnchor, secondaryAnchor, preference, affinity) {
const myWidget = this._widgets[widget.getId()];
myWidget.setPosition(primaryAnchor, secondaryAnchor, preference, affinity);
this.setShouldRender();
}
removeWidget(widget) {
const widgetId = widget.getId();
if (this._widgets.hasOwnProperty(widgetId)) {
const myWidget = this._widgets[widgetId];
delete this._widgets[widgetId];
const domNode = myWidget.domNode.domNode;
domNode.remove();
domNode.removeAttribute('monaco-visible-content-widget');
this.setShouldRender();
}
}
shouldSuppressMouseDownOnWidget(widgetId) {
if (this._widgets.hasOwnProperty(widgetId)) {
return this._widgets[widgetId].suppressMouseDown;
}
return false;
}
onBeforeRender(viewportData) {
const keys = Object.keys(this._widgets);
for (const widgetId of keys) {
this._widgets[widgetId].onBeforeRender(viewportData);
}
}
prepareRender(ctx) {
const keys = Object.keys(this._widgets);
for (const widgetId of keys) {
this._widgets[widgetId].prepareRender(ctx);
}
}
render(ctx) {
const keys = Object.keys(this._widgets);
for (const widgetId of keys) {
this._widgets[widgetId].render(ctx);
}
}
}
class Widget {
constructor(context, viewDomNode, actual) {
this._primaryAnchor = new PositionPair(null, null);
this._secondaryAnchor = new PositionPair(null, null);
this._context = context;
this._viewDomNode = viewDomNode;
this._actual = actual;
this.domNode = createFastDomNode(this._actual.getDomNode());
this.id = this._actual.getId();
this.allowEditorOverflow = this._actual.allowEditorOverflow || false;
this.suppressMouseDown = this._actual.suppressMouseDown || false;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._fixedOverflowWidgets = options.get(42 /* EditorOption.fixedOverflowWidgets */);
this._contentWidth = layoutInfo.contentWidth;
this._contentLeft = layoutInfo.contentLeft;
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._affinity = null;
this._preference = [];
this._cachedDomNodeOffsetWidth = -1;
this._cachedDomNodeOffsetHeight = -1;
this._maxWidth = this._getMaxWidth();
this._isVisible = false;
this._renderData = null;
this.domNode.setPosition((this._fixedOverflowWidgets && this.allowEditorOverflow) ? 'fixed' : 'absolute');
this.domNode.setDisplay('none');
this.domNode.setVisibility('hidden');
this.domNode.setAttribute('widgetId', this.id);
this.domNode.setMaxWidth(this._maxWidth);
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._contentLeft = layoutInfo.contentLeft;
this._contentWidth = layoutInfo.contentWidth;
this._maxWidth = this._getMaxWidth();
}
}
updateAnchorViewPosition() {
this._setPosition(this._affinity, this._primaryAnchor.modelPosition, this._secondaryAnchor.modelPosition);
}
_setPosition(affinity, primaryAnchor, secondaryAnchor) {
this._affinity = affinity;
this._primaryAnchor = getValidPositionPair(primaryAnchor, this._context.viewModel, this._affinity);
this._secondaryAnchor = getValidPositionPair(secondaryAnchor, this._context.viewModel, this._affinity);
function getValidPositionPair(position, viewModel, affinity) {
if (!position) {
return new PositionPair(null, null);
}
// Do not trust that widgets give a valid position
const validModelPosition = viewModel.model.validatePosition(position);
if (viewModel.coordinatesConverter.modelPositionIsVisible(validModelPosition)) {
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(validModelPosition, affinity ?? undefined);
return new PositionPair(position, viewPosition);
}
return new PositionPair(position, null);
}
}
_getMaxWidth() {
const elDocument = this.domNode.domNode.ownerDocument;
const elWindow = elDocument.defaultView;
return (this.allowEditorOverflow
? elWindow?.innerWidth || elDocument.documentElement.offsetWidth || elDocument.body.offsetWidth
: this._contentWidth);
}
setPosition(primaryAnchor, secondaryAnchor, preference, affinity) {
this._setPosition(affinity, primaryAnchor, secondaryAnchor);
this._preference = preference;
if (this._primaryAnchor.viewPosition && this._preference && this._preference.length > 0) {
// this content widget would like to be visible if possible
// we change it from `display:none` to `display:block` even if it
// might be outside the viewport such that we can measure its size
// in `prepareRender`
this.domNode.setDisplay('block');
}
else {
this.domNode.setDisplay('none');
}
this._cachedDomNodeOffsetWidth = -1;
this._cachedDomNodeOffsetHeight = -1;
}
_layoutBoxInViewport(anchor, width, height, ctx) {
// Our visible box is split horizontally by the current line => 2 boxes
// a) the box above the line
const aboveLineTop = anchor.top;
const heightAvailableAboveLine = aboveLineTop;
// b) the box under the line
const underLineTop = anchor.top + anchor.height;
const heightAvailableUnderLine = ctx.viewportHeight - underLineTop;
const aboveTop = aboveLineTop - height;
const fitsAbove = (heightAvailableAboveLine >= height);
const belowTop = underLineTop;
const fitsBelow = (heightAvailableUnderLine >= height);
// And its left
let left = anchor.left;
if (left + width > ctx.scrollLeft + ctx.viewportWidth) {
left = ctx.scrollLeft + ctx.viewportWidth - width;
}
if (left < ctx.scrollLeft) {
left = ctx.scrollLeft;
}
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
}
_layoutHorizontalSegmentInPage(windowSize, domNodePosition, left, width) {
// Leave some clearance to the left/right
const LEFT_PADDING = 15;
const RIGHT_PADDING = 15;
// Initially, the limits are defined as the dom node limits
const MIN_LIMIT = Math.max(LEFT_PADDING, domNodePosition.left - width);
const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width - RIGHT_PADDING);
const elDocument = this._viewDomNode.domNode.ownerDocument;
const elWindow = elDocument.defaultView;
let absoluteLeft = domNodePosition.left + left - (elWindow?.scrollX ?? 0);
if (absoluteLeft + width > MAX_LIMIT) {
const delta = absoluteLeft - (MAX_LIMIT - width);
absoluteLeft -= delta;
left -= delta;
}
if (absoluteLeft < MIN_LIMIT) {
const delta = absoluteLeft - MIN_LIMIT;
absoluteLeft -= delta;
left -= delta;
}
return [left, absoluteLeft];
}
_layoutBoxInPage(anchor, width, height, ctx) {
const aboveTop = anchor.top - height;
const belowTop = anchor.top + anchor.height;
const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
const elDocument = this._viewDomNode.domNode.ownerDocument;
const elWindow = elDocument.defaultView;
const absoluteAboveTop = domNodePosition.top + aboveTop - (elWindow?.scrollY ?? 0);
const absoluteBelowTop = domNodePosition.top + belowTop - (elWindow?.scrollY ?? 0);
const windowSize = dom.getClientArea(elDocument.body);
const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width);
// Leave some clearance to the top/bottom
const TOP_PADDING = 22;
const BOTTOM_PADDING = 22;
const fitsAbove = (absoluteAboveTop >= TOP_PADDING);
const fitsBelow = (absoluteBelowTop + height <= windowSize.height - BOTTOM_PADDING);
if (this._fixedOverflowWidgets) {
return {
fitsAbove,
aboveTop: Math.max(absoluteAboveTop, TOP_PADDING),
fitsBelow,
belowTop: absoluteBelowTop,
left: absoluteAboveLeft
};
}
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
}
_prepareRenderWidgetAtExactPositionOverflowing(topLeft) {
return new Coordinate(topLeft.top, topLeft.left + this._contentLeft);
}
/**
* Compute the coordinates above and below the primary and secondary anchors.
* The content widget *must* touch the primary anchor.
* The content widget should touch if possible the secondary anchor.
*/
_getAnchorsCoordinates(ctx) {
const primary = getCoordinates(this._primaryAnchor.viewPosition, this._affinity, this._lineHeight);
const secondaryViewPosition = (this._secondaryAnchor.viewPosition?.lineNumber === this._primaryAnchor.viewPosition?.lineNumber ? this._secondaryAnchor.viewPosition : null);
const secondary = getCoordinates(secondaryViewPosition, this._affinity, this._lineHeight);
return { primary, secondary };
function getCoordinates(position, affinity, lineHeight) {
if (!position) {
return null;
}
const horizontalPosition = ctx.visibleRangeForPosition(position);
if (!horizontalPosition) {
return null;
}
// Left-align widgets that should appear :before content
const left = (position.column === 1 && affinity === 3 /* PositionAffinity.LeftOfInjectedText */ ? 0 : horizontalPosition.left);
const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.scrollTop;
return new AnchorCoordinate(top, left, lineHeight);
}
}
_reduceAnchorCoordinates(primary, secondary, width) {
if (!secondary) {
return primary;
}
const fontInfo = this._context.configuration.options.get(50 /* EditorOption.fontInfo */);
let left = secondary.left;
if (left < primary.left) {
left = Math.max(left, primary.left - width + fontInfo.typicalFullwidthCharacterWidth);
}
else {
left = Math.min(left, primary.left + width - fontInfo.typicalFullwidthCharacterWidth);
}
return new AnchorCoordinate(primary.top, left, primary.height);
}
_prepareRenderWidget(ctx) {
if (!this._preference || this._preference.length === 0) {
return null;
}
const { primary, secondary } = this._getAnchorsCoordinates(ctx);
if (!primary) {
return {
kind: 'offViewport',
preserveFocus: this.domNode.domNode.contains(this.domNode.domNode.ownerDocument.activeElement)
};
// return null;
}
if (this._cachedDomNodeOffsetWidth === -1 || this._cachedDomNodeOffsetHeight === -1) {
let preferredDimensions = null;
if (typeof this._actual.beforeRender === 'function') {
preferredDimensions = safeInvoke(this._actual.beforeRender, this._actual);
}
if (preferredDimensions) {
this._cachedDomNodeOffsetWidth = preferredDimensions.width;
this._cachedDomNodeOffsetHeight = preferredDimensions.height;
}
else {
const domNode = this.domNode.domNode;
const clientRect = domNode.getBoundingClientRect();
this._cachedDomNodeOffsetWidth = Math.round(clientRect.width);
this._cachedDomNodeOffsetHeight = Math.round(clientRect.height);
}
}
const anchor = this._reduceAnchorCoordinates(primary, secondary, this._cachedDomNodeOffsetWidth);
let placement;
if (this.allowEditorOverflow) {
placement = this._layoutBoxInPage(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
}
else {
placement = this._layoutBoxInViewport(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
}
// Do two passes, first for perfect fit, second picks first option
for (let pass = 1; pass <= 2; pass++) {
for (const pref of this._preference) {
// placement
if (pref === 1 /* ContentWidgetPositionPreference.ABOVE */) {
if (!placement) {
// Widget outside of viewport
return null;
}
if (pass === 2 || placement.fitsAbove) {
return {
kind: 'inViewport',
coordinate: new Coordinate(placement.aboveTop, placement.left),
position: 1 /* ContentWidgetPositionPreference.ABOVE */
};
}
}
else if (pref === 2 /* ContentWidgetPositionPreference.BELOW */) {
if (!placement) {
// Widget outside of viewport
return null;
}
if (pass === 2 || placement.fitsBelow) {
return {
kind: 'inViewport',
coordinate: new Coordinate(placement.belowTop, placement.left),
position: 2 /* ContentWidgetPositionPreference.BELOW */
};
}
}
else {
if (this.allowEditorOverflow) {
return {
kind: 'inViewport',
coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(new Coordinate(anchor.top, anchor.left)),
position: 0 /* ContentWidgetPositionPreference.EXACT */
};
}
else {
return {
kind: 'inViewport',
coordinate: new Coordinate(anchor.top, anchor.left),
position: 0 /* ContentWidgetPositionPreference.EXACT */
};
}
}
}
}
return null;
}
/**
* On this first pass, we ensure that the content widget (if it is in the viewport) has the max width set correctly.
*/
onBeforeRender(viewportData) {
if (!this._primaryAnchor.viewPosition || !this._preference) {
return;
}
if (this._primaryAnchor.viewPosition.lineNumber < viewportData.startLineNumber || this._primaryAnchor.viewPosition.lineNumber > viewportData.endLineNumber) {
// Outside of viewport
return;
}
this.domNode.setMaxWidth(this._maxWidth);
}
prepareRender(ctx) {
this._renderData = this._prepareRenderWidget(ctx);
}
render(ctx) {
if (!this._renderData || this._renderData.kind === 'offViewport') {
// This widget should be invisible
if (this._isVisible) {
this.domNode.removeAttribute('monaco-visible-content-widget');
this._isVisible = false;
if (this._renderData?.kind === 'offViewport' && this._renderData.preserveFocus) {
// widget wants to be shown, but it is outside of the viewport and it
// has focus which we need to preserve
this.domNode.setTop(-1000);
}
else {
this.domNode.setVisibility('hidden');
}
}
if (typeof this._actual.afterRender === 'function') {
safeInvoke(this._actual.afterRender, this._actual, null);
}
return;
}
// This widget should be visible
if (this.allowEditorOverflow) {
this.domNode.setTop(this._renderData.coordinate.top);
this.domNode.setLeft(this._renderData.coordinate.left);
}
else {
this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop - ctx.bigNumbersDelta);
this.domNode.setLeft(this._renderData.coordinate.left);
}
if (!this._isVisible) {
this.domNode.setVisibility('inherit');
this.domNode.setAttribute('monaco-visible-content-widget', 'true');
this._isVisible = true;
}
if (typeof this._actual.afterRender === 'function') {
safeInvoke(this._actual.afterRender, this._actual, this._renderData.position);
}
}
}
class PositionPair {
constructor(modelPosition, viewPosition) {
this.modelPosition = modelPosition;
this.viewPosition = viewPosition;
}
}
class Coordinate {
constructor(top, left) {
this.top = top;
this.left = left;
this._coordinateBrand = undefined;
}
}
class AnchorCoordinate {
constructor(top, left, height) {
this.top = top;
this.left = left;
this.height = height;
this._anchorCoordinateBrand = undefined;
}
}
function safeInvoke(fn, thisArg, ...args) {
try {
return fn.call(thisArg, ...args);
}
catch {
// ignore
return null;
}
}
@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .view-overlays .current-line {
display: block;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
height: 100%;
}
.monaco-editor .margin-view-overlays .current-line {
display: block;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
height: 100%;
}
.monaco-editor
.margin-view-overlays
.current-line.current-line-margin.current-line-margin-both {
border-right: 0;
}
@@ -0,0 +1,196 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './currentLineHighlight.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { editorLineHighlight, editorLineHighlightBorder } from '../../../common/core/editorColorRegistry.js';
import * as arrays from '../../../../base/common/arrays.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { Selection } from '../../../common/core/selection.js';
import { isHighContrast } from '../../../../platform/theme/common/theme.js';
import { Position } from '../../../common/core/position.js';
export class AbstractLineHighlightOverlay extends DynamicViewOverlay {
constructor(context) {
super();
this._context = context;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._renderLineHighlight = options.get(97 /* EditorOption.renderLineHighlight */);
this._renderLineHighlightOnlyWhenFocus = options.get(98 /* EditorOption.renderLineHighlightOnlyWhenFocus */);
this._wordWrap = layoutInfo.isViewportWrapping;
this._contentLeft = layoutInfo.contentLeft;
this._contentWidth = layoutInfo.contentWidth;
this._selectionIsEmpty = true;
this._focused = false;
this._cursorLineNumbers = [1];
this._selections = [new Selection(1, 1, 1, 1)];
this._renderData = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
super.dispose();
}
_readFromSelections() {
let hasChanged = false;
const lineNumbers = new Set();
for (const selection of this._selections) {
lineNumbers.add(selection.positionLineNumber);
}
const cursorsLineNumbers = Array.from(lineNumbers);
cursorsLineNumbers.sort((a, b) => a - b);
if (!arrays.equals(this._cursorLineNumbers, cursorsLineNumbers)) {
this._cursorLineNumbers = cursorsLineNumbers;
hasChanged = true;
}
const selectionIsEmpty = this._selections.every(s => s.isEmpty());
if (this._selectionIsEmpty !== selectionIsEmpty) {
this._selectionIsEmpty = selectionIsEmpty;
hasChanged = true;
}
return hasChanged;
}
// --- begin event handlers
onThemeChanged(e) {
return this._readFromSelections();
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._renderLineHighlight = options.get(97 /* EditorOption.renderLineHighlight */);
this._renderLineHighlightOnlyWhenFocus = options.get(98 /* EditorOption.renderLineHighlightOnlyWhenFocus */);
this._wordWrap = layoutInfo.isViewportWrapping;
this._contentLeft = layoutInfo.contentLeft;
this._contentWidth = layoutInfo.contentWidth;
return true;
}
onCursorStateChanged(e) {
this._selections = e.selections;
return this._readFromSelections();
}
onFlushed(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollWidthChanged || e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
onFocusChanged(e) {
if (!this._renderLineHighlightOnlyWhenFocus) {
return false;
}
this._focused = e.isFocused;
return true;
}
// --- end event handlers
prepareRender(ctx) {
if (!this._shouldRenderThis()) {
this._renderData = null;
return;
}
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
// initialize renderData
const renderData = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
renderData[lineIndex] = '';
}
if (this._wordWrap) {
// do a first pass to render wrapped lines
const renderedLineWrapped = this._renderOne(ctx, false);
for (const cursorLineNumber of this._cursorLineNumbers) {
const coordinatesConverter = this._context.viewModel.coordinatesConverter;
const modelLineNumber = coordinatesConverter.convertViewPositionToModelPosition(new Position(cursorLineNumber, 1)).lineNumber;
const firstViewLineNumber = coordinatesConverter.convertModelPositionToViewPosition(new Position(modelLineNumber, 1)).lineNumber;
const lastViewLineNumber = coordinatesConverter.convertModelPositionToViewPosition(new Position(modelLineNumber, this._context.viewModel.model.getLineMaxColumn(modelLineNumber))).lineNumber;
const firstLine = Math.max(firstViewLineNumber, visibleStartLineNumber);
const lastLine = Math.min(lastViewLineNumber, visibleEndLineNumber);
for (let lineNumber = firstLine; lineNumber <= lastLine; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
renderData[lineIndex] = renderedLineWrapped;
}
}
}
// do a second pass to render exact lines
const renderedLineExact = this._renderOne(ctx, true);
for (const cursorLineNumber of this._cursorLineNumbers) {
if (cursorLineNumber < visibleStartLineNumber || cursorLineNumber > visibleEndLineNumber) {
continue;
}
const lineIndex = cursorLineNumber - visibleStartLineNumber;
renderData[lineIndex] = renderedLineExact;
}
this._renderData = renderData;
}
render(startLineNumber, lineNumber) {
if (!this._renderData) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex >= this._renderData.length) {
return '';
}
return this._renderData[lineIndex];
}
_shouldRenderInMargin() {
return ((this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all')
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused));
}
_shouldRenderInContent() {
return ((this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all')
&& this._selectionIsEmpty
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused));
}
}
export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay {
_renderOne(ctx, exact) {
const className = 'current-line' + (this._shouldRenderInMargin() ? ' current-line-both' : '') + (exact ? ' current-line-exact' : '');
return `<div class="${className}" style="width:${Math.max(ctx.scrollWidth, this._contentWidth)}px;"></div>`;
}
_shouldRenderThis() {
return this._shouldRenderInContent();
}
_shouldRenderOther() {
return this._shouldRenderInMargin();
}
}
export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay {
_renderOne(ctx, exact) {
const className = 'current-line' + (this._shouldRenderInMargin() ? ' current-line-margin' : '') + (this._shouldRenderOther() ? ' current-line-margin-both' : '') + (this._shouldRenderInMargin() && exact ? ' current-line-exact-margin' : '');
return `<div class="${className}" style="width:${this._contentLeft}px"></div>`;
}
_shouldRenderThis() {
return true;
}
_shouldRenderOther() {
return this._shouldRenderInContent();
}
}
registerThemingParticipant((theme, collector) => {
const lineHighlight = theme.getColor(editorLineHighlight);
if (lineHighlight) {
collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${lineHighlight}; }`);
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`);
}
if (!lineHighlight || lineHighlight.isTransparent() || theme.defines(editorLineHighlightBorder)) {
const lineHighlightBorder = theme.getColor(editorLineHighlightBorder);
if (lineHighlightBorder) {
collector.addRule(`.monaco-editor .view-overlays .current-line-exact { border: 2px solid ${lineHighlightBorder}; }`);
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-exact-margin { border: 2px solid ${lineHighlightBorder}; }`);
if (isHighContrast(theme.type)) {
collector.addRule(`.monaco-editor .view-overlays .current-line-exact { border-width: 1px; }`);
collector.addRule(`.monaco-editor .margin-view-overlays .current-line-exact-margin { border-width: 1px; }`);
}
}
}
});
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cdr = core decorations rendering (div)
*/
.monaco-editor .lines-content .cdr {
position: absolute;
height: 100%;
}
@@ -0,0 +1,193 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './decorations.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { HorizontalRange } from '../../view/renderingContext.js';
import { Range } from '../../../common/core/range.js';
export class DecorationsOverlay extends DynamicViewOverlay {
constructor(context) {
super();
this._context = context;
const options = this._context.configuration.options;
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
return true;
}
onDecorationsChanged(e) {
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged || e.scrollWidthChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
prepareRender(ctx) {
const _decorations = ctx.getDecorationsInViewport();
// Keep only decorations with `className`
let decorations = [];
let decorationsLen = 0;
for (let i = 0, len = _decorations.length; i < len; i++) {
const d = _decorations[i];
if (d.options.className) {
decorations[decorationsLen++] = d;
}
}
// Sort decorations for consistent render output
decorations = decorations.sort((a, b) => {
if (a.options.zIndex < b.options.zIndex) {
return -1;
}
if (a.options.zIndex > b.options.zIndex) {
return 1;
}
const aClassName = a.options.className;
const bClassName = b.options.className;
if (aClassName < bClassName) {
return -1;
}
if (aClassName > bClassName) {
return 1;
}
return Range.compareRangesUsingStarts(a.range, b.range);
});
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = '';
}
// Render first whole line decorations and then regular decorations
this._renderWholeLineDecorations(ctx, decorations, output);
this._renderNormalDecorations(ctx, decorations, output);
this._renderResult = output;
}
_renderWholeLineDecorations(ctx, decorations, output) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
const d = decorations[i];
if (!d.options.isWholeLine) {
continue;
}
const decorationOutput = ('<div class="cdr '
+ d.options.className
+ '" style="left:0;width:100%;"></div>');
const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber);
const endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber);
for (let j = startLineNumber; j <= endLineNumber; j++) {
const lineIndex = j - visibleStartLineNumber;
output[lineIndex] += decorationOutput;
}
}
}
_renderNormalDecorations(ctx, decorations, output) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
let prevClassName = null;
let prevShowIfCollapsed = false;
let prevRange = null;
let prevShouldFillLineOnLineBreak = false;
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
const d = decorations[i];
if (d.options.isWholeLine) {
continue;
}
const className = d.options.className;
const showIfCollapsed = Boolean(d.options.showIfCollapsed);
let range = d.range;
if (showIfCollapsed && range.endColumn === 1 && range.endLineNumber !== range.startLineNumber) {
range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber - 1, this._context.viewModel.getLineMaxColumn(range.endLineNumber - 1));
}
if (prevClassName === className && prevShowIfCollapsed === showIfCollapsed && Range.areIntersectingOrTouching(prevRange, range)) {
// merge into previous decoration
prevRange = Range.plusRange(prevRange, range);
continue;
}
// flush previous decoration
if (prevClassName !== null) {
this._renderNormalDecoration(ctx, prevRange, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, visibleStartLineNumber, output);
}
prevClassName = className;
prevShowIfCollapsed = showIfCollapsed;
prevRange = range;
prevShouldFillLineOnLineBreak = d.options.shouldFillLineOnLineBreak ?? false;
}
if (prevClassName !== null) {
this._renderNormalDecoration(ctx, prevRange, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, visibleStartLineNumber, output);
}
}
_renderNormalDecoration(ctx, range, className, shouldFillLineOnLineBreak, showIfCollapsed, visibleStartLineNumber, output) {
const linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/ className === 'findMatch');
if (!linesVisibleRanges) {
return;
}
for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) {
const lineVisibleRanges = linesVisibleRanges[j];
if (lineVisibleRanges.outsideRenderedLine) {
continue;
}
const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber;
if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) {
const singleVisibleRange = lineVisibleRanges.ranges[0];
if (singleVisibleRange.width < this._typicalHalfwidthCharacterWidth) {
// collapsed/very small range case => make the decoration visible by expanding its width
// expand its size on both sides (both to the left and to the right, keeping it centered)
const center = Math.round(singleVisibleRange.left + singleVisibleRange.width / 2);
const left = Math.max(0, Math.round(center - this._typicalHalfwidthCharacterWidth / 2));
lineVisibleRanges.ranges[0] = new HorizontalRange(left, this._typicalHalfwidthCharacterWidth);
}
}
for (let k = 0, lenK = lineVisibleRanges.ranges.length; k < lenK; k++) {
const expandToLeft = shouldFillLineOnLineBreak && lineVisibleRanges.continuesOnNextLine && lenK === 1;
const visibleRange = lineVisibleRanges.ranges[k];
const decorationOutput = ('<div class="cdr '
+ className
+ '" style="left:'
+ String(visibleRange.left)
+ 'px;width:'
+ (expandToLeft ?
'100%;' :
(String(visibleRange.width) + 'px;'))
+ '"></div>');
output[lineIndex] += decorationOutput;
}
}
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* 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 { SmoothScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
import { getThemeTypeSelector } from '../../../../platform/theme/common/themeService.js';
export class EditorScrollbar extends ViewPart {
constructor(context, linesContent, viewDomNode, overflowGuardDomNode) {
super(context);
const options = this._context.configuration.options;
const scrollbar = options.get(104 /* EditorOption.scrollbar */);
const mouseWheelScrollSensitivity = options.get(75 /* EditorOption.mouseWheelScrollSensitivity */);
const fastScrollSensitivity = options.get(40 /* EditorOption.fastScrollSensitivity */);
const scrollPredominantAxis = options.get(107 /* EditorOption.scrollPredominantAxis */);
const scrollbarOptions = {
listenOnDomNode: viewDomNode.domNode,
className: 'editor-scrollable' + ' ' + getThemeTypeSelector(context.theme.type),
useShadows: false,
lazyRender: true,
vertical: scrollbar.vertical,
horizontal: scrollbar.horizontal,
verticalHasArrows: scrollbar.verticalHasArrows,
horizontalHasArrows: scrollbar.horizontalHasArrows,
verticalScrollbarSize: scrollbar.verticalScrollbarSize,
verticalSliderSize: scrollbar.verticalSliderSize,
horizontalScrollbarSize: scrollbar.horizontalScrollbarSize,
horizontalSliderSize: scrollbar.horizontalSliderSize,
handleMouseWheel: scrollbar.handleMouseWheel,
alwaysConsumeMouseWheel: scrollbar.alwaysConsumeMouseWheel,
arrowSize: scrollbar.arrowSize,
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity,
fastScrollSensitivity: fastScrollSensitivity,
scrollPredominantAxis: scrollPredominantAxis,
scrollByPage: scrollbar.scrollByPage,
};
this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable()));
PartFingerprints.write(this.scrollbar.getDomNode(), 6 /* PartFingerprint.ScrollableElement */);
this.scrollbarDomNode = createFastDomNode(this.scrollbar.getDomNode());
this.scrollbarDomNode.setPosition('absolute');
this._setLayout();
// When having a zone widget that calls .focus() on one of its dom elements,
// the browser will try desperately to reveal that dom node, unexpectedly
// changing the .scrollTop of this.linesContent
const onBrowserDesperateReveal = (domNode, lookAtScrollTop, lookAtScrollLeft) => {
const newScrollPosition = {};
if (lookAtScrollTop) {
const deltaTop = domNode.scrollTop;
if (deltaTop) {
newScrollPosition.scrollTop = this._context.viewLayout.getCurrentScrollTop() + deltaTop;
domNode.scrollTop = 0;
}
}
if (lookAtScrollLeft) {
const deltaLeft = domNode.scrollLeft;
if (deltaLeft) {
newScrollPosition.scrollLeft = this._context.viewLayout.getCurrentScrollLeft() + deltaLeft;
domNode.scrollLeft = 0;
}
}
this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, 1 /* ScrollType.Immediate */);
};
// I've seen this happen both on the view dom node & on the lines content dom node.
this._register(dom.addDisposableListener(viewDomNode.domNode, 'scroll', (e) => onBrowserDesperateReveal(viewDomNode.domNode, true, true)));
this._register(dom.addDisposableListener(linesContent.domNode, 'scroll', (e) => onBrowserDesperateReveal(linesContent.domNode, true, false)));
this._register(dom.addDisposableListener(overflowGuardDomNode.domNode, 'scroll', (e) => onBrowserDesperateReveal(overflowGuardDomNode.domNode, true, false)));
this._register(dom.addDisposableListener(this.scrollbarDomNode.domNode, 'scroll', (e) => onBrowserDesperateReveal(this.scrollbarDomNode.domNode, true, false)));
}
dispose() {
super.dispose();
}
_setLayout() {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this.scrollbarDomNode.setLeft(layoutInfo.contentLeft);
const minimap = options.get(73 /* EditorOption.minimap */);
const side = minimap.side;
if (side === 'right') {
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimap.minimapWidth);
}
else {
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth);
}
this.scrollbarDomNode.setHeight(layoutInfo.height);
}
getOverviewRulerLayoutInfo() {
return this.scrollbar.getOverviewRulerLayoutInfo();
}
getDomNode() {
return this.scrollbarDomNode;
}
delegateVerticalScrollbarPointerDown(browserEvent) {
this.scrollbar.delegateVerticalScrollbarPointerDown(browserEvent);
}
delegateScrollFromMouseWheelEvent(browserEvent) {
this.scrollbar.delegateScrollFromMouseWheelEvent(browserEvent);
}
// --- begin event handlers
onConfigurationChanged(e) {
if (e.hasChanged(104 /* EditorOption.scrollbar */)
|| e.hasChanged(75 /* EditorOption.mouseWheelScrollSensitivity */)
|| e.hasChanged(40 /* EditorOption.fastScrollSensitivity */)) {
const options = this._context.configuration.options;
const scrollbar = options.get(104 /* EditorOption.scrollbar */);
const mouseWheelScrollSensitivity = options.get(75 /* EditorOption.mouseWheelScrollSensitivity */);
const fastScrollSensitivity = options.get(40 /* EditorOption.fastScrollSensitivity */);
const scrollPredominantAxis = options.get(107 /* EditorOption.scrollPredominantAxis */);
const newOpts = {
vertical: scrollbar.vertical,
horizontal: scrollbar.horizontal,
verticalScrollbarSize: scrollbar.verticalScrollbarSize,
horizontalScrollbarSize: scrollbar.horizontalScrollbarSize,
scrollByPage: scrollbar.scrollByPage,
handleMouseWheel: scrollbar.handleMouseWheel,
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity,
fastScrollSensitivity: fastScrollSensitivity,
scrollPredominantAxis: scrollPredominantAxis
};
this.scrollbar.updateOptions(newOpts);
}
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
this._setLayout();
}
return true;
}
onScrollChanged(e) {
return true;
}
onThemeChanged(e) {
this.scrollbar.updateClassName('editor-scrollable' + ' ' + getThemeTypeSelector(this._context.theme.type));
return true;
}
// --- end event handlers
prepareRender(ctx) {
// Nothing to do
}
render(ctx) {
this.scrollbar.renderNow();
}
}
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .glyph-margin {
position: absolute;
top: 0;
}
/*
Keeping name short for faster parsing.
cgmr = core glyph margin rendering (div)
*/
.monaco-editor .glyph-margin-widgets .cgmr {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
/*
Ensure spinning icons are pixel-perfectly centered and avoid wobble.
This is only applied to icons that spin to avoid unnecessary
GPU layers and blurry subpixel AA.
*/
.monaco-editor .glyph-margin-widgets .cgmr.codicon-modifier-spin::before {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@@ -0,0 +1,398 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { ArrayQueue } from '../../../../base/common/arrays.js';
import './glyphMargin.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { ViewPart } from '../../view/viewPart.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { GlyphMarginLane } from '../../../common/model.js';
/**
* Represents a decoration that should be shown along the lines from `startLineNumber` to `endLineNumber`.
* This can end up producing multiple `LineDecorationToRender`.
*/
export class DecorationToRender {
constructor(startLineNumber, endLineNumber, className, tooltip, zIndex) {
this.startLineNumber = startLineNumber;
this.endLineNumber = endLineNumber;
this.className = className;
this.tooltip = tooltip;
this._decorationToRenderBrand = undefined;
this.zIndex = zIndex ?? 0;
}
}
/**
* A decoration that should be shown along a line.
*/
export class LineDecorationToRender {
constructor(className, zIndex, tooltip) {
this.className = className;
this.zIndex = zIndex;
this.tooltip = tooltip;
}
}
/**
* Decorations to render on a visible line.
*/
export class VisibleLineDecorationsToRender {
constructor() {
this.decorations = [];
}
add(decoration) {
this.decorations.push(decoration);
}
getDecorations() {
return this.decorations;
}
}
export class DedupOverlay extends DynamicViewOverlay {
/**
* Returns an array with an element for each visible line number.
*/
_render(visibleStartLineNumber, visibleEndLineNumber, decorations) {
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = new VisibleLineDecorationsToRender();
}
if (decorations.length === 0) {
return output;
}
// Sort decorations by className, then by startLineNumber and then by endLineNumber
decorations.sort((a, b) => {
if (a.className === b.className) {
if (a.startLineNumber === b.startLineNumber) {
return a.endLineNumber - b.endLineNumber;
}
return a.startLineNumber - b.startLineNumber;
}
return (a.className < b.className ? -1 : 1);
});
let prevClassName = null;
let prevEndLineIndex = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
const d = decorations[i];
const className = d.className;
const zIndex = d.zIndex;
let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber;
const endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber;
if (prevClassName === className) {
// Here we avoid rendering the same className multiple times on the same line
startLineIndex = Math.max(prevEndLineIndex + 1, startLineIndex);
prevEndLineIndex = Math.max(prevEndLineIndex, endLineIndex);
}
else {
prevClassName = className;
prevEndLineIndex = endLineIndex;
}
for (let i = startLineIndex; i <= prevEndLineIndex; i++) {
output[i].add(new LineDecorationToRender(className, zIndex, d.tooltip));
}
}
return output;
}
}
export class GlyphMarginWidgets extends ViewPart {
constructor(context) {
super(context);
this._widgets = {};
this._context = context;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName('glyph-margin-widgets');
this.domNode.setPosition('absolute');
this.domNode.setTop(0);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._glyphMargin = options.get(57 /* EditorOption.glyphMargin */);
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
this._glyphMarginDecorationLaneCount = layoutInfo.glyphMarginDecorationLaneCount;
this._managedDomNodes = [];
this._decorationGlyphsToRender = [];
}
dispose() {
this._managedDomNodes = [];
this._decorationGlyphsToRender = [];
this._widgets = {};
super.dispose();
}
getWidgets() {
return Object.values(this._widgets);
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._glyphMargin = options.get(57 /* EditorOption.glyphMargin */);
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
this._glyphMarginDecorationLaneCount = layoutInfo.glyphMarginDecorationLaneCount;
return true;
}
onDecorationsChanged(e) {
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
// --- begin widget management
addWidget(widget) {
const domNode = createFastDomNode(widget.getDomNode());
this._widgets[widget.getId()] = {
widget: widget,
preference: widget.getPosition(),
domNode: domNode,
renderInfo: null
};
domNode.setPosition('absolute');
domNode.setDisplay('none');
domNode.setAttribute('widgetId', widget.getId());
this.domNode.appendChild(domNode);
this.setShouldRender();
}
setWidgetPosition(widget, preference) {
const myWidget = this._widgets[widget.getId()];
if (myWidget.preference.lane === preference.lane
&& myWidget.preference.zIndex === preference.zIndex
&& Range.equalsRange(myWidget.preference.range, preference.range)) {
return false;
}
myWidget.preference = preference;
this.setShouldRender();
return true;
}
removeWidget(widget) {
const widgetId = widget.getId();
if (this._widgets[widgetId]) {
const widgetData = this._widgets[widgetId];
const domNode = widgetData.domNode.domNode;
delete this._widgets[widgetId];
domNode.remove();
this.setShouldRender();
}
}
// --- end widget management
_collectDecorationBasedGlyphRenderRequest(ctx, requests) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const decorations = ctx.getDecorationsInViewport();
for (const d of decorations) {
const glyphMarginClassName = d.options.glyphMarginClassName;
if (!glyphMarginClassName) {
continue;
}
const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber);
const endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber);
const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center;
const zIndex = d.options.zIndex ?? 0;
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 0));
const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(lane);
requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, laneIndex, zIndex, glyphMarginClassName));
}
}
}
_collectWidgetBasedGlyphRenderRequest(ctx, requests) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
for (const widget of Object.values(this._widgets)) {
const range = widget.preference.range;
const { startLineNumber, endLineNumber } = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(Range.lift(range));
if (!startLineNumber || !endLineNumber || endLineNumber < visibleStartLineNumber || startLineNumber > visibleEndLineNumber) {
// The widget is not in the viewport
continue;
}
// The widget is in the viewport, find a good line for it
const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber);
const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(widgetLineNumber, 0));
const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane);
requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, laneIndex, widget.preference.zIndex, widget));
}
}
_collectSortedGlyphRenderRequests(ctx) {
const requests = [];
this._collectDecorationBasedGlyphRenderRequest(ctx, requests);
this._collectWidgetBasedGlyphRenderRequest(ctx, requests);
// sort requests by lineNumber ASC, lane ASC, zIndex DESC, type DESC (widgets first), className ASC
// don't change this sort unless you understand `prepareRender` below.
requests.sort((a, b) => {
if (a.lineNumber === b.lineNumber) {
if (a.laneIndex === b.laneIndex) {
if (a.zIndex === b.zIndex) {
if (b.type === a.type) {
if (a.type === 0 /* GlyphRenderRequestType.Decoration */ && b.type === 0 /* GlyphRenderRequestType.Decoration */) {
return (a.className < b.className ? -1 : 1);
}
return 0;
}
return b.type - a.type;
}
return b.zIndex - a.zIndex;
}
return a.laneIndex - b.laneIndex;
}
return a.lineNumber - b.lineNumber;
});
return requests;
}
/**
* Will store render information in each widget's renderInfo and in `_decorationGlyphsToRender`.
*/
prepareRender(ctx) {
if (!this._glyphMargin) {
this._decorationGlyphsToRender = [];
return;
}
for (const widget of Object.values(this._widgets)) {
widget.renderInfo = null;
}
const requests = new ArrayQueue(this._collectSortedGlyphRenderRequests(ctx));
const decorationGlyphsToRender = [];
while (requests.length > 0) {
const first = requests.peek();
if (!first) {
// not possible
break;
}
// Requests are sorted by lineNumber and lane, so we read all requests for this particular location
const requestsAtLocation = requests.takeWhile((el) => el.lineNumber === first.lineNumber && el.laneIndex === first.laneIndex);
if (!requestsAtLocation || requestsAtLocation.length === 0) {
// not possible
break;
}
const winner = requestsAtLocation[0];
if (winner.type === 0 /* GlyphRenderRequestType.Decoration */) {
// combine all decorations with the same z-index
const classNames = [];
// requests are sorted by zIndex, type, and className so we can dedup className by looking at the previous one
for (const request of requestsAtLocation) {
if (request.zIndex !== winner.zIndex || request.type !== winner.type) {
break;
}
if (classNames.length === 0 || classNames[classNames.length - 1] !== request.className) {
classNames.push(request.className);
}
}
decorationGlyphsToRender.push(winner.accept(classNames.join(' '))); // TODO@joyceerhl Implement overflow for remaining decorations
}
else {
// widgets cannot be combined
winner.widget.renderInfo = {
lineNumber: winner.lineNumber,
laneIndex: winner.laneIndex,
};
}
}
this._decorationGlyphsToRender = decorationGlyphsToRender;
}
render(ctx) {
if (!this._glyphMargin) {
for (const widget of Object.values(this._widgets)) {
widget.domNode.setDisplay('none');
}
while (this._managedDomNodes.length > 0) {
const domNode = this._managedDomNodes.pop();
domNode?.domNode.remove();
}
return;
}
const width = (Math.round(this._glyphMarginWidth / this._glyphMarginDecorationLaneCount));
// Render widgets
for (const widget of Object.values(this._widgets)) {
if (!widget.renderInfo) {
// this widget is not visible
widget.domNode.setDisplay('none');
}
else {
const top = ctx.viewportData.relativeVerticalOffset[widget.renderInfo.lineNumber - ctx.viewportData.startLineNumber];
const left = this._glyphMarginLeft + widget.renderInfo.laneIndex * this._lineHeight;
widget.domNode.setDisplay('block');
widget.domNode.setTop(top);
widget.domNode.setLeft(left);
widget.domNode.setWidth(width);
widget.domNode.setHeight(this._lineHeight);
}
}
// Render decorations, reusing previous dom nodes as possible
for (let i = 0; i < this._decorationGlyphsToRender.length; i++) {
const dec = this._decorationGlyphsToRender[i];
const top = ctx.viewportData.relativeVerticalOffset[dec.lineNumber - ctx.viewportData.startLineNumber];
const left = this._glyphMarginLeft + dec.laneIndex * this._lineHeight;
let domNode;
if (i < this._managedDomNodes.length) {
domNode = this._managedDomNodes[i];
}
else {
domNode = createFastDomNode(document.createElement('div'));
this._managedDomNodes.push(domNode);
this.domNode.appendChild(domNode);
}
domNode.setClassName(`cgmr codicon ` + dec.combinedClassName);
domNode.setPosition(`absolute`);
domNode.setTop(top);
domNode.setLeft(left);
domNode.setWidth(width);
domNode.setHeight(this._lineHeight);
}
// remove extra dom nodes
while (this._managedDomNodes.length > this._decorationGlyphsToRender.length) {
const domNode = this._managedDomNodes.pop();
domNode?.domNode.remove();
}
}
}
/**
* A request to render a decoration in the glyph margin at a certain location.
*/
class DecorationBasedGlyphRenderRequest {
constructor(lineNumber, laneIndex, zIndex, className) {
this.lineNumber = lineNumber;
this.laneIndex = laneIndex;
this.zIndex = zIndex;
this.className = className;
this.type = 0 /* GlyphRenderRequestType.Decoration */;
}
accept(combinedClassName) {
return new DecorationBasedGlyph(this.lineNumber, this.laneIndex, combinedClassName);
}
}
/**
* A request to render a widget in the glyph margin at a certain location.
*/
class WidgetBasedGlyphRenderRequest {
constructor(lineNumber, laneIndex, zIndex, widget) {
this.lineNumber = lineNumber;
this.laneIndex = laneIndex;
this.zIndex = zIndex;
this.widget = widget;
this.type = 1 /* GlyphRenderRequestType.Widget */;
}
}
class DecorationBasedGlyph {
constructor(lineNumber, laneIndex, combinedClassName) {
this.lineNumber = lineNumber;
this.laneIndex = laneIndex;
this.combinedClassName = combinedClassName;
}
}
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .lines-content .core-guide {
position: absolute;
box-sizing: border-box;
height: 100%;
}
@@ -0,0 +1,247 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './indentGuides.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorBracketPairGuideActiveBackground1, editorBracketPairGuideActiveBackground2, editorBracketPairGuideActiveBackground3, editorBracketPairGuideActiveBackground4, editorBracketPairGuideActiveBackground5, editorBracketPairGuideActiveBackground6, editorBracketPairGuideBackground1, editorBracketPairGuideBackground2, editorBracketPairGuideBackground3, editorBracketPairGuideBackground4, editorBracketPairGuideBackground5, editorBracketPairGuideBackground6, editorIndentGuide1, editorIndentGuide2, editorIndentGuide3, editorIndentGuide4, editorIndentGuide5, editorIndentGuide6, editorActiveIndentGuide1, editorActiveIndentGuide2, editorActiveIndentGuide3, editorActiveIndentGuide4, editorActiveIndentGuide5, editorActiveIndentGuide6 } from '../../../common/core/editorColorRegistry.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { Position } from '../../../common/core/position.js';
import { ArrayQueue } from '../../../../base/common/arrays.js';
import { isDefined } from '../../../../base/common/types.js';
import { BracketPairGuidesClassNames } from '../../../common/model/guidesTextModelPart.js';
import { IndentGuide, HorizontalGuidesState } from '../../../common/textModelGuides.js';
export class IndentGuidesOverlay extends DynamicViewOverlay {
constructor(context) {
super();
this._context = context;
this._primaryPosition = null;
const options = this._context.configuration.options;
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._spaceWidth = fontInfo.spaceWidth;
this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth);
this._bracketPairGuideOptions = options.get(16 /* EditorOption.guides */);
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._spaceWidth = fontInfo.spaceWidth;
this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth);
this._bracketPairGuideOptions = options.get(16 /* EditorOption.guides */);
return true;
}
onCursorStateChanged(e) {
const selection = e.selections[0];
const newPosition = selection.getPosition();
if (!this._primaryPosition?.equals(newPosition)) {
this._primaryPosition = newPosition;
return true;
}
return false;
}
onDecorationsChanged(e) {
// true for inline decorations
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged; // || e.scrollWidthChanged;
}
onZonesChanged(e) {
return true;
}
onLanguageConfigurationChanged(e) {
return true;
}
// --- end event handlers
prepareRender(ctx) {
if (!this._bracketPairGuideOptions.indentation && this._bracketPairGuideOptions.bracketPairs === false) {
this._renderResult = null;
return;
}
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const scrollWidth = ctx.scrollWidth;
const activeCursorPosition = this._primaryPosition;
const indents = this.getGuidesByLine(visibleStartLineNumber, Math.min(visibleEndLineNumber + 1, this._context.viewModel.getLineCount()), activeCursorPosition);
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const indent = indents[lineIndex];
let result = '';
const leftOffset = ctx.visibleRangeForPosition(new Position(lineNumber, 1))?.left ?? 0;
for (const guide of indent) {
const left = guide.column === -1
? leftOffset + (guide.visibleColumn - 1) * this._spaceWidth
: ctx.visibleRangeForPosition(new Position(lineNumber, guide.column)).left;
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
break;
}
const className = guide.horizontalLine ? (guide.horizontalLine.top ? 'horizontal-top' : 'horizontal-bottom') : 'vertical';
const width = guide.horizontalLine
? (ctx.visibleRangeForPosition(new Position(lineNumber, guide.horizontalLine.endColumn))?.left ?? (left + this._spaceWidth)) - left
: this._spaceWidth;
result += `<div class="core-guide ${guide.className} ${className}" style="left:${left}px;width:${width}px"></div>`;
}
output[lineIndex] = result;
}
this._renderResult = output;
}
getGuidesByLine(visibleStartLineNumber, visibleEndLineNumber, activeCursorPosition) {
const bracketGuides = this._bracketPairGuideOptions.bracketPairs !== false
? this._context.viewModel.getBracketGuidesInRangeByLine(visibleStartLineNumber, visibleEndLineNumber, activeCursorPosition, {
highlightActive: this._bracketPairGuideOptions.highlightActiveBracketPair,
horizontalGuides: this._bracketPairGuideOptions.bracketPairsHorizontal === true
? HorizontalGuidesState.Enabled
: this._bracketPairGuideOptions.bracketPairsHorizontal === 'active'
? HorizontalGuidesState.EnabledForActive
: HorizontalGuidesState.Disabled,
includeInactive: this._bracketPairGuideOptions.bracketPairs === true,
})
: null;
const indentGuides = this._bracketPairGuideOptions.indentation
? this._context.viewModel.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber)
: null;
let activeIndentStartLineNumber = 0;
let activeIndentEndLineNumber = 0;
let activeIndentLevel = 0;
if (this._bracketPairGuideOptions.highlightActiveIndentation !== false && activeCursorPosition) {
const activeIndentInfo = this._context.viewModel.getActiveIndentGuide(activeCursorPosition.lineNumber, visibleStartLineNumber, visibleEndLineNumber);
activeIndentStartLineNumber = activeIndentInfo.startLineNumber;
activeIndentEndLineNumber = activeIndentInfo.endLineNumber;
activeIndentLevel = activeIndentInfo.indent;
}
const { indentSize } = this._context.viewModel.model.getOptions();
const result = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineGuides = new Array();
result.push(lineGuides);
const bracketGuidesInLine = bracketGuides ? bracketGuides[lineNumber - visibleStartLineNumber] : [];
const bracketGuidesInLineQueue = new ArrayQueue(bracketGuidesInLine);
const indentGuidesInLine = indentGuides ? indentGuides[lineNumber - visibleStartLineNumber] : 0;
for (let indentLvl = 1; indentLvl <= indentGuidesInLine; indentLvl++) {
const indentGuide = (indentLvl - 1) * indentSize + 1;
const isActive =
// Disable active indent guide if there are bracket guides.
(this._bracketPairGuideOptions.highlightActiveIndentation === 'always' || bracketGuidesInLine.length === 0) &&
activeIndentStartLineNumber <= lineNumber &&
lineNumber <= activeIndentEndLineNumber &&
indentLvl === activeIndentLevel;
lineGuides.push(...bracketGuidesInLineQueue.takeWhile(g => g.visibleColumn < indentGuide) || []);
const peeked = bracketGuidesInLineQueue.peek();
if (!peeked || peeked.visibleColumn !== indentGuide || peeked.horizontalLine) {
lineGuides.push(new IndentGuide(indentGuide, -1, `core-guide-indent lvl-${(indentLvl - 1) % 30}` + (isActive ? ' indent-active' : ''), null, -1, -1));
}
}
lineGuides.push(...bracketGuidesInLineQueue.takeWhile(g => true) || []);
}
return result;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
function transparentToUndefined(color) {
if (color && color.isTransparent()) {
return undefined;
}
return color;
}
registerThemingParticipant((theme, collector) => {
const colors = [
{ bracketColor: editorBracketHighlightingForeground1, guideColor: editorBracketPairGuideBackground1, guideColorActive: editorBracketPairGuideActiveBackground1 },
{ bracketColor: editorBracketHighlightingForeground2, guideColor: editorBracketPairGuideBackground2, guideColorActive: editorBracketPairGuideActiveBackground2 },
{ bracketColor: editorBracketHighlightingForeground3, guideColor: editorBracketPairGuideBackground3, guideColorActive: editorBracketPairGuideActiveBackground3 },
{ bracketColor: editorBracketHighlightingForeground4, guideColor: editorBracketPairGuideBackground4, guideColorActive: editorBracketPairGuideActiveBackground4 },
{ bracketColor: editorBracketHighlightingForeground5, guideColor: editorBracketPairGuideBackground5, guideColorActive: editorBracketPairGuideActiveBackground5 },
{ bracketColor: editorBracketHighlightingForeground6, guideColor: editorBracketPairGuideBackground6, guideColorActive: editorBracketPairGuideActiveBackground6 }
];
const colorProvider = new BracketPairGuidesClassNames();
const indentColors = [
{ indentColor: editorIndentGuide1, indentColorActive: editorActiveIndentGuide1 },
{ indentColor: editorIndentGuide2, indentColorActive: editorActiveIndentGuide2 },
{ indentColor: editorIndentGuide3, indentColorActive: editorActiveIndentGuide3 },
{ indentColor: editorIndentGuide4, indentColorActive: editorActiveIndentGuide4 },
{ indentColor: editorIndentGuide5, indentColorActive: editorActiveIndentGuide5 },
{ indentColor: editorIndentGuide6, indentColorActive: editorActiveIndentGuide6 },
];
const colorValues = colors
.map(c => {
const bracketColor = theme.getColor(c.bracketColor);
const guideColor = theme.getColor(c.guideColor);
const guideColorActive = theme.getColor(c.guideColorActive);
const effectiveGuideColor = transparentToUndefined(transparentToUndefined(guideColor) ?? bracketColor?.transparent(0.3));
const effectiveGuideColorActive = transparentToUndefined(transparentToUndefined(guideColorActive) ?? bracketColor);
if (!effectiveGuideColor || !effectiveGuideColorActive) {
return undefined;
}
return {
guideColor: effectiveGuideColor,
guideColorActive: effectiveGuideColorActive,
};
})
.filter(isDefined);
const indentColorValues = indentColors
.map(c => {
const indentColor = theme.getColor(c.indentColor);
const indentColorActive = theme.getColor(c.indentColorActive);
const effectiveIndentColor = transparentToUndefined(indentColor);
const effectiveIndentColorActive = transparentToUndefined(indentColorActive);
if (!effectiveIndentColor || !effectiveIndentColorActive) {
return undefined;
}
return {
indentColor: effectiveIndentColor,
indentColorActive: effectiveIndentColorActive,
};
})
.filter(isDefined);
if (colorValues.length > 0) {
for (let level = 0; level < 30; level++) {
const colors = colorValues[level % colorValues.length];
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')} { --guide-color: ${colors.guideColor}; --guide-color-active: ${colors.guideColorActive}; }`);
}
collector.addRule(`.monaco-editor .vertical { box-shadow: 1px 0 0 0 var(--guide-color) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top { border-top: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .horizontal-bottom { border-bottom: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .vertical.${colorProvider.activeClassName} { box-shadow: 1px 0 0 0 var(--guide-color-active) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top.${colorProvider.activeClassName} { border-top: 1px solid var(--guide-color-active); }`);
collector.addRule(`.monaco-editor .horizontal-bottom.${colorProvider.activeClassName} { border-bottom: 1px solid var(--guide-color-active); }`);
}
if (indentColorValues.length > 0) {
for (let level = 0; level < 30; level++) {
const colors = indentColorValues[level % indentColorValues.length];
collector.addRule(`.monaco-editor .lines-content .core-guide-indent.lvl-${level} { --indent-color: ${colors.indentColor}; --indent-color-active: ${colors.indentColorActive}; }`);
}
collector.addRule(`.monaco-editor .lines-content .core-guide-indent { box-shadow: 1px 0 0 0 var(--indent-color) inset; }`);
collector.addRule(`.monaco-editor .lines-content .core-guide-indent.indent-active { box-shadow: 1px 0 0 0 var(--indent-color-active) inset; }`);
}
});
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .margin-view-overlays .line-numbers {
bottom: 0;
font-variant-numeric: tabular-nums;
position: absolute;
text-align: right;
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
cursor: default;
}
.monaco-editor .relative-current-line-number {
text-align: left;
display: inline-block;
width: 100%;
}
.monaco-editor .margin-view-overlays .line-numbers.lh-odd {
margin-top: 1px;
}
.monaco-editor .line-numbers {
color: var(--vscode-editorLineNumber-foreground);
}
.monaco-editor .line-numbers.active-line-number {
color: var(--vscode-editorLineNumber-activeForeground);
}
@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './lineNumbers.css';
import * as platform from '../../../../base/common/platform.js';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { editorDimmedLineNumber, editorLineNumbers } from '../../../common/core/editorColorRegistry.js';
export class LineNumbersOverlay extends DynamicViewOverlay {
static { this.CLASS_NAME = 'line-numbers'; }
constructor(context) {
super();
this._context = context;
this._readConfig();
this._lastCursorModelPosition = new Position(1, 1);
this._renderResult = null;
this._activeLineNumber = 1;
this._context.addEventHandler(this);
}
_readConfig() {
const options = this._context.configuration.options;
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
const lineNumbers = options.get(68 /* EditorOption.lineNumbers */);
this._renderLineNumbers = lineNumbers.renderType;
this._renderCustomLineNumbers = lineNumbers.renderFn;
this._renderFinalNewline = options.get(96 /* EditorOption.renderFinalNewline */);
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineNumbersLeft = layoutInfo.lineNumbersLeft;
this._lineNumbersWidth = layoutInfo.lineNumbersWidth;
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
this._readConfig();
return true;
}
onCursorStateChanged(e) {
const primaryViewPosition = e.selections[0].getPosition();
this._lastCursorModelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition);
let shouldRender = false;
if (this._activeLineNumber !== primaryViewPosition.lineNumber) {
this._activeLineNumber = primaryViewPosition.lineNumber;
shouldRender = true;
}
if (this._renderLineNumbers === 2 /* RenderLineNumbersType.Relative */ || this._renderLineNumbers === 3 /* RenderLineNumbersType.Interval */) {
shouldRender = true;
}
return shouldRender;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
onDecorationsChanged(e) {
return e.affectsLineNumber;
}
// --- end event handlers
_getLineRenderLineNumber(viewLineNumber) {
const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(viewLineNumber, 1));
if (modelPosition.column !== 1) {
return '';
}
const modelLineNumber = modelPosition.lineNumber;
if (this._renderCustomLineNumbers) {
return this._renderCustomLineNumbers(modelLineNumber);
}
if (this._renderLineNumbers === 2 /* RenderLineNumbersType.Relative */) {
const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber);
if (diff === 0) {
return '<span class="relative-current-line-number">' + modelLineNumber + '</span>';
}
return String(diff);
}
if (this._renderLineNumbers === 3 /* RenderLineNumbersType.Interval */) {
if (this._lastCursorModelPosition.lineNumber === modelLineNumber) {
return String(modelLineNumber);
}
if (modelLineNumber % 10 === 0) {
return String(modelLineNumber);
}
const finalLineNumber = this._context.viewModel.getLineCount();
if (modelLineNumber === finalLineNumber) {
return String(modelLineNumber);
}
return '';
}
return String(modelLineNumber);
}
prepareRender(ctx) {
if (this._renderLineNumbers === 0 /* RenderLineNumbersType.Off */) {
this._renderResult = null;
return;
}
const lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : '');
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const lineNoDecorations = this._context.viewModel.getDecorationsInViewport(ctx.visibleRange).filter(d => !!d.options.lineNumberClassName);
lineNoDecorations.sort((a, b) => Range.compareRangesUsingEnds(a.range, b.range));
let decorationStartIndex = 0;
const lineCount = this._context.viewModel.getLineCount();
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
let renderLineNumber = this._getLineRenderLineNumber(lineNumber);
let extraClassNames = '';
// skip decorations whose end positions we've already passed
while (decorationStartIndex < lineNoDecorations.length && lineNoDecorations[decorationStartIndex].range.endLineNumber < lineNumber) {
decorationStartIndex++;
}
for (let i = decorationStartIndex; i < lineNoDecorations.length; i++) {
const { range, options } = lineNoDecorations[i];
if (range.startLineNumber <= lineNumber) {
extraClassNames += ' ' + options.lineNumberClassName;
}
}
if (!renderLineNumber && !extraClassNames) {
output[lineIndex] = '';
continue;
}
if (lineNumber === lineCount && this._context.viewModel.getLineLength(lineNumber) === 0) {
// this is the last line
if (this._renderFinalNewline === 'off') {
renderLineNumber = '';
}
if (this._renderFinalNewline === 'dimmed') {
extraClassNames += ' dimmed-line-number';
}
}
if (lineNumber === this._activeLineNumber) {
extraClassNames += ' active-line-number';
}
output[lineIndex] = (`<div class="${LineNumbersOverlay.CLASS_NAME}${lineHeightClassName}${extraClassNames}" style="left:${this._lineNumbersLeft}px;width:${this._lineNumbersWidth}px;">${renderLineNumber}</div>`);
}
this._renderResult = output;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
registerThemingParticipant((theme, collector) => {
const editorLineNumbersColor = theme.getColor(editorLineNumbers);
const editorDimmedLineNumberColor = theme.getColor(editorDimmedLineNumber);
if (editorDimmedLineNumberColor) {
collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorDimmedLineNumberColor}; }`);
}
else if (editorLineNumbersColor) {
collector.addRule(`.monaco-editor .line-numbers.dimmed-line-number { color: ${editorLineNumbersColor.transparent(0.4)}; }`);
}
});
@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class DomReadingContext {
get didDomLayout() {
return this._didDomLayout;
}
readClientRect() {
if (!this._clientRectRead) {
this._clientRectRead = true;
const rect = this._domNode.getBoundingClientRect();
this.markDidDomLayout();
this._clientRectDeltaLeft = rect.left;
this._clientRectScale = rect.width / this._domNode.offsetWidth;
}
}
get clientRectDeltaLeft() {
if (!this._clientRectRead) {
this.readClientRect();
}
return this._clientRectDeltaLeft;
}
get clientRectScale() {
if (!this._clientRectRead) {
this.readClientRect();
}
return this._clientRectScale;
}
constructor(_domNode, endNode) {
this._domNode = _domNode;
this.endNode = endNode;
this._didDomLayout = false;
this._clientRectDeltaLeft = 0;
this._clientRectScale = 1;
this._clientRectRead = false;
}
markDidDomLayout() {
this._didDomLayout = true;
}
}
@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FloatHorizontalRange } from '../../view/renderingContext.js';
export class RangeUtil {
static _createRange() {
if (!this._handyReadyRange) {
this._handyReadyRange = document.createRange();
}
return this._handyReadyRange;
}
static _detachRange(range, endNode) {
// Move range out of the span node, IE doesn't like having many ranges in
// the same spot and will act badly for lines containing dashes ('-')
range.selectNodeContents(endNode);
}
static _readClientRects(startElement, startOffset, endElement, endOffset, endNode) {
const range = this._createRange();
try {
range.setStart(startElement, startOffset);
range.setEnd(endElement, endOffset);
return range.getClientRects();
}
catch (e) {
// This is life ...
return null;
}
finally {
this._detachRange(range, endNode);
}
}
static _mergeAdjacentRanges(ranges) {
if (ranges.length === 1) {
// There is nothing to merge
return ranges;
}
ranges.sort(FloatHorizontalRange.compare);
const result = [];
let resultLen = 0;
let prev = ranges[0];
for (let i = 1, len = ranges.length; i < len; i++) {
const range = ranges[i];
if (prev.left + prev.width + 0.9 /* account for browser's rounding errors*/ >= range.left) {
prev.width = Math.max(prev.width, range.left + range.width - prev.left);
}
else {
result[resultLen++] = prev;
prev = range;
}
}
result[resultLen++] = prev;
return result;
}
static _createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, clientRectScale) {
if (!clientRects || clientRects.length === 0) {
return null;
}
// We go through FloatHorizontalRange because it has been observed in bi-di text
// that the clientRects are not coming in sorted from the browser
const result = [];
for (let i = 0, len = clientRects.length; i < len; i++) {
const clientRect = clientRects[i];
result[i] = new FloatHorizontalRange(Math.max(0, (clientRect.left - clientRectDeltaLeft) / clientRectScale), clientRect.width / clientRectScale);
}
return this._mergeAdjacentRanges(result);
}
static readHorizontalRanges(domNode, startChildIndex, startOffset, endChildIndex, endOffset, context) {
// Panic check
const min = 0;
const max = domNode.children.length - 1;
if (min > max) {
return null;
}
startChildIndex = Math.min(max, Math.max(min, startChildIndex));
endChildIndex = Math.min(max, Math.max(min, endChildIndex));
if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0 && !domNode.children[startChildIndex].firstChild) {
// We must find the position at the beginning of a <span>
// To cover cases of empty <span>s, avoid using a range and use the <span>'s bounding box
const clientRects = domNode.children[startChildIndex].getClientRects();
context.markDidDomLayout();
return this._createHorizontalRangesFromClientRects(clientRects, context.clientRectDeltaLeft, context.clientRectScale);
}
// If crossing over to a span only to select offset 0, then use the previous span's maximum offset
// Chrome is buggy and doesn't handle 0 offsets well sometimes.
if (startChildIndex !== endChildIndex) {
if (endChildIndex > 0 && endOffset === 0) {
endChildIndex--;
endOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
}
}
let startElement = domNode.children[startChildIndex].firstChild;
let endElement = domNode.children[endChildIndex].firstChild;
if (!startElement || !endElement) {
// When having an empty <span> (without any text content), try to move to the previous <span>
if (!startElement && startOffset === 0 && startChildIndex > 0) {
startElement = domNode.children[startChildIndex - 1].firstChild;
startOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
}
if (!endElement && endOffset === 0 && endChildIndex > 0) {
endElement = domNode.children[endChildIndex - 1].firstChild;
endOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
}
}
if (!startElement || !endElement) {
return null;
}
startOffset = Math.min(startElement.textContent.length, Math.max(0, startOffset));
endOffset = Math.min(endElement.textContent.length, Math.max(0, endOffset));
const clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, context.endNode);
context.markDidDomLayout();
return this._createHorizontalRangesFromClientRects(clientRects, context.clientRectDeltaLeft, context.clientRectScale);
}
}
@@ -0,0 +1,543 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as browser from '../../../../base/browser/browser.js';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import * as platform from '../../../../base/common/platform.js';
import { RangeUtil } from './rangeUtil.js';
import { FloatHorizontalRange, VisibleRanges } from '../../view/renderingContext.js';
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
import { RenderLineInput, renderViewLine, LineRange, DomPosition } from '../../../common/viewLayout/viewLineRenderer.js';
import { isHighContrast } from '../../../../platform/theme/common/theme.js';
import { EditorFontLigatures } from '../../../common/config/editorOptions.js';
const canUseFastRenderedViewLine = (function () {
if (platform.isNative) {
// In VSCode we know very well when the zoom level changes
return true;
}
if (platform.isLinux || browser.isFirefox || browser.isSafari) {
// On Linux, it appears that zooming affects char widths (in pixels), which is unexpected.
// --
// Even though we read character widths correctly, having read them at a specific zoom level
// does not mean they are the same at the current zoom level.
// --
// This could be improved if we ever figure out how to get an event when browsers zoom,
// but until then we have to stick with reading client rects.
// --
// The same has been observed with Firefox on Windows7
// --
// The same has been oversved with Safari
return false;
}
return true;
})();
let monospaceAssumptionsAreValid = true;
export class ViewLineOptions {
constructor(config, themeType) {
this.themeType = themeType;
const options = config.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const experimentalWhitespaceRendering = options.get(38 /* EditorOption.experimentalWhitespaceRendering */);
if (experimentalWhitespaceRendering === 'off') {
this.renderWhitespace = options.get(100 /* EditorOption.renderWhitespace */);
}
else {
// whitespace is rendered in a different layer
this.renderWhitespace = 'none';
}
this.renderControlCharacters = options.get(95 /* EditorOption.renderControlCharacters */);
this.spaceWidth = fontInfo.spaceWidth;
this.middotWidth = fontInfo.middotWidth;
this.wsmiddotWidth = fontInfo.wsmiddotWidth;
this.useMonospaceOptimizations = (fontInfo.isMonospace
&& !options.get(33 /* EditorOption.disableMonospaceOptimizations */));
this.canUseHalfwidthRightwardsArrow = fontInfo.canUseHalfwidthRightwardsArrow;
this.lineHeight = options.get(67 /* EditorOption.lineHeight */);
this.stopRenderingLineAfter = options.get(118 /* EditorOption.stopRenderingLineAfter */);
this.fontLigatures = options.get(51 /* EditorOption.fontLigatures */);
}
equals(other) {
return (this.themeType === other.themeType
&& this.renderWhitespace === other.renderWhitespace
&& this.renderControlCharacters === other.renderControlCharacters
&& this.spaceWidth === other.spaceWidth
&& this.middotWidth === other.middotWidth
&& this.wsmiddotWidth === other.wsmiddotWidth
&& this.useMonospaceOptimizations === other.useMonospaceOptimizations
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
&& this.lineHeight === other.lineHeight
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.fontLigatures === other.fontLigatures);
}
}
export class ViewLine {
static { this.CLASS_NAME = 'view-line'; }
constructor(options) {
this._options = options;
this._isMaybeInvalid = true;
this._renderedViewLine = null;
}
// --- begin IVisibleLineData
getDomNode() {
if (this._renderedViewLine && this._renderedViewLine.domNode) {
return this._renderedViewLine.domNode.domNode;
}
return null;
}
setDomNode(domNode) {
if (this._renderedViewLine) {
this._renderedViewLine.domNode = createFastDomNode(domNode);
}
else {
throw new Error('I have no rendered view line to set the dom node to...');
}
}
onContentChanged() {
this._isMaybeInvalid = true;
}
onTokensChanged() {
this._isMaybeInvalid = true;
}
onDecorationsChanged() {
this._isMaybeInvalid = true;
}
onOptionsChanged(newOptions) {
this._isMaybeInvalid = true;
this._options = newOptions;
}
onSelectionChanged() {
if (isHighContrast(this._options.themeType) || this._options.renderWhitespace === 'selection') {
this._isMaybeInvalid = true;
return true;
}
return false;
}
renderLine(lineNumber, deltaTop, lineHeight, viewportData, sb) {
if (this._isMaybeInvalid === false) {
// it appears that nothing relevant has changed
return false;
}
this._isMaybeInvalid = false;
const lineData = viewportData.getViewLineRenderingData(lineNumber);
const options = this._options;
const actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn);
// Only send selection information when needed for rendering whitespace
let selectionsOnLine = null;
if (isHighContrast(options.themeType) || this._options.renderWhitespace === 'selection') {
const selections = viewportData.selections;
for (const selection of selections) {
if (selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber) {
// Selection does not intersect line
continue;
}
const startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn);
const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn);
if (startColumn < endColumn) {
if (isHighContrast(options.themeType)) {
actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', 0 /* InlineDecorationType.Regular */));
}
if (this._options.renderWhitespace === 'selection') {
if (!selectionsOnLine) {
selectionsOnLine = [];
}
selectionsOnLine.push(new LineRange(startColumn - 1, endColumn - 1));
}
}
}
}
const renderLineInput = new RenderLineInput(options.useMonospaceOptimizations, options.canUseHalfwidthRightwardsArrow, lineData.content, lineData.continuesWithWrappedLine, lineData.isBasicASCII, lineData.containsRTL, lineData.minColumn - 1, lineData.tokens, actualInlineDecorations, lineData.tabSize, lineData.startVisibleColumn, options.spaceWidth, options.middotWidth, options.wsmiddotWidth, options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, options.fontLigatures !== EditorFontLigatures.OFF, selectionsOnLine);
if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) {
// no need to do anything, we have the same render input
return false;
}
sb.appendString('<div style="top:');
sb.appendString(String(deltaTop));
sb.appendString('px;height:');
sb.appendString(String(lineHeight));
sb.appendString('px;" class="');
sb.appendString(ViewLine.CLASS_NAME);
sb.appendString('">');
const output = renderViewLine(renderLineInput, sb);
sb.appendString('</div>');
let renderedViewLine = null;
if (monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === 0 /* ForeignElementType.None */) {
renderedViewLine = new FastRenderedViewLine(this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping);
}
if (!renderedViewLine) {
renderedViewLine = createRenderedLine(this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping, output.containsRTL, output.containsForeignElements);
}
this._renderedViewLine = renderedViewLine;
return true;
}
layoutLine(lineNumber, deltaTop, lineHeight) {
if (this._renderedViewLine && this._renderedViewLine.domNode) {
this._renderedViewLine.domNode.setTop(deltaTop);
this._renderedViewLine.domNode.setHeight(lineHeight);
}
}
// --- end IVisibleLineData
getWidth(context) {
if (!this._renderedViewLine) {
return 0;
}
return this._renderedViewLine.getWidth(context);
}
getWidthIsFast() {
if (!this._renderedViewLine) {
return true;
}
return this._renderedViewLine.getWidthIsFast();
}
needsMonospaceFontCheck() {
if (!this._renderedViewLine) {
return false;
}
return (this._renderedViewLine instanceof FastRenderedViewLine);
}
monospaceAssumptionsAreValid() {
if (!this._renderedViewLine) {
return monospaceAssumptionsAreValid;
}
if (this._renderedViewLine instanceof FastRenderedViewLine) {
return this._renderedViewLine.monospaceAssumptionsAreValid();
}
return monospaceAssumptionsAreValid;
}
onMonospaceAssumptionsInvalidated() {
if (this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine) {
this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine();
}
}
getVisibleRangesForRange(lineNumber, startColumn, endColumn, context) {
if (!this._renderedViewLine) {
return null;
}
startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn));
endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn));
const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter;
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1) {
// This range is obviously not visible
return new VisibleRanges(true, [new FloatHorizontalRange(this.getWidth(context), 0)]);
}
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1) {
startColumn = stopRenderingLineAfter + 1;
}
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter + 1) {
endColumn = stopRenderingLineAfter + 1;
}
const horizontalRanges = this._renderedViewLine.getVisibleRangesForRange(lineNumber, startColumn, endColumn, context);
if (horizontalRanges && horizontalRanges.length > 0) {
return new VisibleRanges(false, horizontalRanges);
}
return null;
}
getColumnOfNodeOffset(spanNode, offset) {
if (!this._renderedViewLine) {
return 1;
}
return this._renderedViewLine.getColumnOfNodeOffset(spanNode, offset);
}
}
/**
* A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font.
*/
class FastRenderedViewLine {
constructor(domNode, renderLineInput, characterMapping) {
this._cachedWidth = -1;
this.domNode = domNode;
this.input = renderLineInput;
const keyColumnCount = Math.floor(renderLineInput.lineContent.length / 300 /* Constants.MaxMonospaceDistance */);
if (keyColumnCount > 0) {
this._keyColumnPixelOffsetCache = new Float32Array(keyColumnCount);
for (let i = 0; i < keyColumnCount; i++) {
this._keyColumnPixelOffsetCache[i] = -1;
}
}
else {
this._keyColumnPixelOffsetCache = null;
}
this._characterMapping = characterMapping;
this._charWidth = renderLineInput.spaceWidth;
}
getWidth(context) {
if (!this.domNode || this.input.lineContent.length < 300 /* Constants.MaxMonospaceDistance */) {
const horizontalOffset = this._characterMapping.getHorizontalOffset(this._characterMapping.length);
return Math.round(this._charWidth * horizontalOffset);
}
if (this._cachedWidth === -1) {
this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;
context?.markDidDomLayout();
}
return this._cachedWidth;
}
getWidthIsFast() {
return (this.input.lineContent.length < 300 /* Constants.MaxMonospaceDistance */) || this._cachedWidth !== -1;
}
monospaceAssumptionsAreValid() {
if (!this.domNode) {
return monospaceAssumptionsAreValid;
}
if (this.input.lineContent.length < 300 /* Constants.MaxMonospaceDistance */) {
const expectedWidth = this.getWidth(null);
const actualWidth = this.domNode.domNode.firstChild.offsetWidth;
if (Math.abs(expectedWidth - actualWidth) >= 2) {
// more than 2px off
console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`);
monospaceAssumptionsAreValid = false;
}
}
return monospaceAssumptionsAreValid;
}
toSlowRenderedLine() {
return createRenderedLine(this.domNode, this.input, this._characterMapping, false, 0 /* ForeignElementType.None */);
}
getVisibleRangesForRange(lineNumber, startColumn, endColumn, context) {
const startPosition = this._getColumnPixelOffset(lineNumber, startColumn, context);
const endPosition = this._getColumnPixelOffset(lineNumber, endColumn, context);
return [new FloatHorizontalRange(startPosition, endPosition - startPosition)];
}
_getColumnPixelOffset(lineNumber, column, context) {
if (column <= 300 /* Constants.MaxMonospaceDistance */) {
const horizontalOffset = this._characterMapping.getHorizontalOffset(column);
return this._charWidth * horizontalOffset;
}
const keyColumnOrdinal = Math.floor((column - 1) / 300 /* Constants.MaxMonospaceDistance */) - 1;
const keyColumn = (keyColumnOrdinal + 1) * 300 /* Constants.MaxMonospaceDistance */ + 1;
let keyColumnPixelOffset = -1;
if (this._keyColumnPixelOffsetCache) {
keyColumnPixelOffset = this._keyColumnPixelOffsetCache[keyColumnOrdinal];
if (keyColumnPixelOffset === -1) {
keyColumnPixelOffset = this._actualReadPixelOffset(lineNumber, keyColumn, context);
this._keyColumnPixelOffsetCache[keyColumnOrdinal] = keyColumnPixelOffset;
}
}
if (keyColumnPixelOffset === -1) {
// Could not read actual key column pixel offset
const horizontalOffset = this._characterMapping.getHorizontalOffset(column);
return this._charWidth * horizontalOffset;
}
const keyColumnHorizontalOffset = this._characterMapping.getHorizontalOffset(keyColumn);
const horizontalOffset = this._characterMapping.getHorizontalOffset(column);
return keyColumnPixelOffset + this._charWidth * (horizontalOffset - keyColumnHorizontalOffset);
}
_getReadingTarget(myDomNode) {
return myDomNode.domNode.firstChild;
}
_actualReadPixelOffset(lineNumber, column, context) {
if (!this.domNode) {
return -1;
}
const domPosition = this._characterMapping.getDomPosition(column);
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(this.domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);
if (!r || r.length === 0) {
return -1;
}
return r[0].left;
}
getColumnOfNodeOffset(spanNode, offset) {
return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);
}
}
/**
* Every time we render a line, we save what we have rendered in an instance of this class.
*/
class RenderedViewLine {
constructor(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) {
this.domNode = domNode;
this.input = renderLineInput;
this._characterMapping = characterMapping;
this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent);
this._containsForeignElements = containsForeignElements;
this._cachedWidth = -1;
this._pixelOffsetCache = null;
if (!containsRTL || this._characterMapping.length === 0 /* the line is empty */) {
this._pixelOffsetCache = new Float32Array(Math.max(2, this._characterMapping.length + 1));
for (let column = 0, len = this._characterMapping.length; column <= len; column++) {
this._pixelOffsetCache[column] = -1;
}
}
}
// --- Reading from the DOM methods
_getReadingTarget(myDomNode) {
return myDomNode.domNode.firstChild;
}
/**
* Width of the line in pixels
*/
getWidth(context) {
if (!this.domNode) {
return 0;
}
if (this._cachedWidth === -1) {
this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;
context?.markDidDomLayout();
}
return this._cachedWidth;
}
getWidthIsFast() {
if (this._cachedWidth === -1) {
return false;
}
return true;
}
/**
* Visible ranges for a model range
*/
getVisibleRangesForRange(lineNumber, startColumn, endColumn, context) {
if (!this.domNode) {
return null;
}
if (this._pixelOffsetCache !== null) {
// the text is LTR
const startOffset = this._readPixelOffset(this.domNode, lineNumber, startColumn, context);
if (startOffset === -1) {
return null;
}
const endOffset = this._readPixelOffset(this.domNode, lineNumber, endColumn, context);
if (endOffset === -1) {
return null;
}
return [new FloatHorizontalRange(startOffset, endOffset - startOffset)];
}
return this._readVisibleRangesForRange(this.domNode, lineNumber, startColumn, endColumn, context);
}
_readVisibleRangesForRange(domNode, lineNumber, startColumn, endColumn, context) {
if (startColumn === endColumn) {
const pixelOffset = this._readPixelOffset(domNode, lineNumber, startColumn, context);
if (pixelOffset === -1) {
return null;
}
else {
return [new FloatHorizontalRange(pixelOffset, 0)];
}
}
else {
return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context);
}
}
_readPixelOffset(domNode, lineNumber, column, context) {
if (this._characterMapping.length === 0) {
// This line has no content
if (this._containsForeignElements === 0 /* ForeignElementType.None */) {
// We can assume the line is really empty
return 0;
}
if (this._containsForeignElements === 2 /* ForeignElementType.After */) {
// We have foreign elements after the (empty) line
return 0;
}
if (this._containsForeignElements === 1 /* ForeignElementType.Before */) {
// We have foreign elements before the (empty) line
return this.getWidth(context);
}
// We have foreign elements before & after the (empty) line
const readingTarget = this._getReadingTarget(domNode);
if (readingTarget.firstChild) {
context.markDidDomLayout();
return readingTarget.firstChild.offsetWidth;
}
else {
return 0;
}
}
if (this._pixelOffsetCache !== null) {
// the text is LTR
const cachedPixelOffset = this._pixelOffsetCache[column];
if (cachedPixelOffset !== -1) {
return cachedPixelOffset;
}
const result = this._actualReadPixelOffset(domNode, lineNumber, column, context);
this._pixelOffsetCache[column] = result;
return result;
}
return this._actualReadPixelOffset(domNode, lineNumber, column, context);
}
_actualReadPixelOffset(domNode, lineNumber, column, context) {
if (this._characterMapping.length === 0) {
// This line has no content
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context);
if (!r || r.length === 0) {
return -1;
}
return r[0].left;
}
if (column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === 0 /* ForeignElementType.None */) {
// This branch helps in the case of whitespace only lines which have a width set
return this.getWidth(context);
}
const domPosition = this._characterMapping.getDomPosition(column);
const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);
if (!r || r.length === 0) {
return -1;
}
const result = r[0].left;
if (this.input.isBasicASCII) {
const horizontalOffset = this._characterMapping.getHorizontalOffset(column);
const expectedResult = Math.round(this.input.spaceWidth * horizontalOffset);
if (Math.abs(expectedResult - result) <= 1) {
return expectedResult;
}
}
return result;
}
_readRawVisibleRangesForRange(domNode, startColumn, endColumn, context) {
if (startColumn === 1 && endColumn === this._characterMapping.length) {
// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line
return [new FloatHorizontalRange(0, this.getWidth(context))];
}
const startDomPosition = this._characterMapping.getDomPosition(startColumn);
const endDomPosition = this._characterMapping.getDomPosition(endColumn);
return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startDomPosition.partIndex, startDomPosition.charIndex, endDomPosition.partIndex, endDomPosition.charIndex, context);
}
/**
* Returns the column for the text found at a specific offset inside a rendered dom node
*/
getColumnOfNodeOffset(spanNode, offset) {
return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);
}
}
class WebKitRenderedViewLine extends RenderedViewLine {
_readVisibleRangesForRange(domNode, lineNumber, startColumn, endColumn, context) {
const output = super._readVisibleRangesForRange(domNode, lineNumber, startColumn, endColumn, context);
if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {
return output;
}
// WebKit is buggy and returns an expanded range (to contain words in some cases)
// The last client rect is enlarged (I think)
if (!this.input.containsRTL) {
// This is an attempt to patch things up
// Find position of last column
const endPixelOffset = this._readPixelOffset(domNode, lineNumber, endColumn, context);
if (endPixelOffset !== -1) {
const lastRange = output[output.length - 1];
if (lastRange.left < endPixelOffset) {
// Trim down the width of the last visible range to not go after the last column's position
lastRange.width = endPixelOffset - lastRange.left;
}
}
}
return output;
}
}
const createRenderedLine = (function () {
if (browser.isWebKit) {
return createWebKitRenderedLine;
}
return createNormalRenderedLine;
})();
function createWebKitRenderedLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) {
return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}
function createNormalRenderedLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) {
return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements);
}
export function getColumnOfNodeOffset(characterMapping, spanNode, offset) {
const spanNodeTextContentLength = spanNode.textContent.length;
let spanIndex = -1;
while (spanNode) {
spanNode = spanNode.previousSibling;
spanIndex++;
}
return characterMapping.getColumn(new DomPosition(spanIndex, offset), spanNodeTextContentLength);
}
@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Uncomment to see lines flashing when they're painted */
/*.monaco-editor .view-lines > .view-line {
background-color: none;
animation-name: flash-background;
animation-duration: 800ms;
}
@keyframes flash-background {
0% { background-color: lightgreen; }
100% { background-color: none }
}*/
.mtkcontrol {
color: rgb(255, 255, 255) !important;
background: rgb(150, 0, 0) !important;
}
.mtkoverflow {
background-color: var(--vscode-button-background, var(--vscode-editor-background));
color: var(--vscode-button-foreground, var(--vscode-editor-foreground));
border-width: 1px;
border-style: solid;
border-color: var(--vscode-contrastBorder);
border-radius: 2px;
padding: 4px;
cursor: pointer;
}
.mtkoverflow:hover {
background-color: var(--vscode-button-hoverBackground);
}
.monaco-editor.no-user-select .lines-content,
.monaco-editor.no-user-select .view-line,
.monaco-editor.no-user-select .view-lines {
user-select: none;
-webkit-user-select: none;
}
/* Use user-select: text for lookup feature on macOS */
/* https://github.com/microsoft/vscode/issues/85632 */
.monaco-editor.mac .lines-content:hover,
.monaco-editor.mac .view-line:hover,
.monaco-editor.mac .view-lines:hover {
user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
}
.monaco-editor.enable-user-select {
user-select: initial;
-webkit-user-select: initial;
}
.monaco-editor .view-lines {
white-space: nowrap;
}
.monaco-editor .view-line {
position: absolute;
width: 100%;
}
/* There are view-lines in view-zones. We have to make sure this rule does not apply to them, as they don't set a line height */
.monaco-editor .lines-content > .view-lines > .view-line > span {
top: 0;
bottom: 0;
position: absolute;
}
.monaco-editor .mtkw {
color: var(--vscode-editorWhitespace-foreground) !important;
}
.monaco-editor .mtkz {
display: inline-block;
color: var(--vscode-editorWhitespace-foreground) !important;
}
/* TODO@tokenization bootstrap fix */
/*.monaco-editor .view-line > span > span {
float: none;
min-height: inherit;
margin-left: inherit;
}*/
@@ -0,0 +1,702 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import * as platform from '../../../../base/common/platform.js';
import './viewLines.css';
import { applyFontInfo } from '../../config/domFontInfo.js';
import { HorizontalPosition, HorizontalRange, LineVisibleRanges } from '../../view/renderingContext.js';
import { VisibleLinesCollection } from '../../view/viewLayer.js';
import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
import { DomReadingContext } from './domReadingContext.js';
import { ViewLine, ViewLineOptions } from './viewLine.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
class LastRenderedData {
constructor() {
this._currentVisibleRange = new Range(1, 1, 1, 1);
}
getCurrentVisibleRange() {
return this._currentVisibleRange;
}
setCurrentVisibleRange(currentVisibleRange) {
this._currentVisibleRange = currentVisibleRange;
}
}
class HorizontalRevealRangeRequest {
constructor(minimalReveal, lineNumber, startColumn, endColumn, startScrollTop, stopScrollTop, scrollType) {
this.minimalReveal = minimalReveal;
this.lineNumber = lineNumber;
this.startColumn = startColumn;
this.endColumn = endColumn;
this.startScrollTop = startScrollTop;
this.stopScrollTop = stopScrollTop;
this.scrollType = scrollType;
this.type = 'range';
this.minLineNumber = lineNumber;
this.maxLineNumber = lineNumber;
}
}
class HorizontalRevealSelectionsRequest {
constructor(minimalReveal, selections, startScrollTop, stopScrollTop, scrollType) {
this.minimalReveal = minimalReveal;
this.selections = selections;
this.startScrollTop = startScrollTop;
this.stopScrollTop = stopScrollTop;
this.scrollType = scrollType;
this.type = 'selections';
let minLineNumber = selections[0].startLineNumber;
let maxLineNumber = selections[0].endLineNumber;
for (let i = 1, len = selections.length; i < len; i++) {
const selection = selections[i];
minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
}
this.minLineNumber = minLineNumber;
this.maxLineNumber = maxLineNumber;
}
}
export class ViewLines extends ViewPart {
/**
* Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport)
*/
static { this.HORIZONTAL_EXTRA_PX = 30; }
constructor(context, linesContent) {
super(context);
this._linesContent = linesContent;
this._textRangeRestingSpot = document.createElement('div');
this._visibleLines = new VisibleLinesCollection(this);
this.domNode = this._visibleLines.domNode;
const conf = this._context.configuration;
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._isViewportWrapping = wrappingInfo.isViewportWrapping;
this._revealHorizontalRightPadding = options.get(101 /* EditorOption.revealHorizontalRightPadding */);
this._cursorSurroundingLines = options.get(29 /* EditorOption.cursorSurroundingLines */);
this._cursorSurroundingLinesStyle = options.get(30 /* EditorOption.cursorSurroundingLinesStyle */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
PartFingerprints.write(this.domNode, 8 /* PartFingerprint.ViewLines */);
this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
applyFontInfo(this.domNode, fontInfo);
// --- width & height
this._maxLineWidth = 0;
this._asyncUpdateLineWidths = new RunOnceScheduler(() => {
this._updateLineWidthsSlow();
}, 200);
this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => {
this._checkMonospaceFontAssumptions();
}, 2000);
this._lastRenderedData = new LastRenderedData();
this._horizontalRevealRequest = null;
// sticky scroll widget
this._stickyScrollEnabled = options.get(116 /* EditorOption.stickyScroll */).enabled;
this._maxNumberStickyLines = options.get(116 /* EditorOption.stickyScroll */).maxLineCount;
}
dispose() {
this._asyncUpdateLineWidths.dispose();
this._asyncCheckMonospaceFontAssumptions.dispose();
super.dispose();
}
getDomNode() {
return this.domNode;
}
// ---- begin IVisibleLinesHost
createVisibleLine() {
return new ViewLine(this._viewLineOptions);
}
// ---- end IVisibleLinesHost
// ---- begin view event handlers
onConfigurationChanged(e) {
this._visibleLines.onConfigurationChanged(e);
if (e.hasChanged(147 /* EditorOption.wrappingInfo */)) {
this._maxLineWidth = 0;
}
const options = this._context.configuration.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._isViewportWrapping = wrappingInfo.isViewportWrapping;
this._revealHorizontalRightPadding = options.get(101 /* EditorOption.revealHorizontalRightPadding */);
this._cursorSurroundingLines = options.get(29 /* EditorOption.cursorSurroundingLines */);
this._cursorSurroundingLinesStyle = options.get(30 /* EditorOption.cursorSurroundingLinesStyle */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
// sticky scroll
this._stickyScrollEnabled = options.get(116 /* EditorOption.stickyScroll */).enabled;
this._maxNumberStickyLines = options.get(116 /* EditorOption.stickyScroll */).maxLineCount;
applyFontInfo(this.domNode, fontInfo);
this._onOptionsMaybeChanged();
if (e.hasChanged(146 /* EditorOption.layoutInfo */)) {
this._maxLineWidth = 0;
}
return true;
}
_onOptionsMaybeChanged() {
const conf = this._context.configuration;
const newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
if (!this._viewLineOptions.equals(newViewLineOptions)) {
this._viewLineOptions = newViewLineOptions;
const startLineNumber = this._visibleLines.getStartLineNumber();
const endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const line = this._visibleLines.getVisibleLine(lineNumber);
line.onOptionsChanged(this._viewLineOptions);
}
return true;
}
return false;
}
onCursorStateChanged(e) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
let r = false;
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r;
}
return r;
}
onDecorationsChanged(e) {
if (true /*e.inlineDecorationsChanged*/) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();
}
}
return true;
}
onFlushed(e) {
const shouldRender = this._visibleLines.onFlushed(e);
this._maxLineWidth = 0;
return shouldRender;
}
onLinesChanged(e) {
return this._visibleLines.onLinesChanged(e);
}
onLinesDeleted(e) {
return this._visibleLines.onLinesDeleted(e);
}
onLinesInserted(e) {
return this._visibleLines.onLinesInserted(e);
}
onRevealRangeRequest(e) {
// Using the future viewport here in order to handle multiple
// incoming reveal range requests that might all desire to be animated
const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.minimalReveal, e.range, e.selections, e.verticalType);
if (desiredScrollTop === -1) {
// marker to abort the reveal range request
return false;
}
// validate the new desired scroll top
let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });
if (e.revealHorizontal) {
if (e.range && e.range.startLineNumber !== e.range.endLineNumber) {
// Two or more lines? => scroll to base (That's how you see most of the two lines)
newScrollPosition = {
scrollTop: newScrollPosition.scrollTop,
scrollLeft: 0
};
}
else if (e.range) {
// We don't necessarily know the horizontal offset of this range since the line might not be in the view...
this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.minimalReveal, e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
}
else if (e.selections && e.selections.length > 0) {
this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.minimalReveal, e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
}
}
else {
this._horizontalRevealRequest = null;
}
const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);
const scrollType = (scrollTopDelta <= this._lineHeight ? 1 /* ScrollType.Immediate */ : e.scrollType);
this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, scrollType);
return true;
}
onScrollChanged(e) {
if (this._horizontalRevealRequest && e.scrollLeftChanged) {
// cancel any outstanding horizontal reveal request if someone else scrolls horizontally.
this._horizontalRevealRequest = null;
}
if (this._horizontalRevealRequest && e.scrollTopChanged) {
const min = Math.min(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
const max = Math.max(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
if (e.scrollTop < min || e.scrollTop > max) {
// cancel any outstanding horizontal reveal request if someone else scrolls vertically.
this._horizontalRevealRequest = null;
}
}
this.domNode.setWidth(e.scrollWidth);
return this._visibleLines.onScrollChanged(e) || true;
}
onTokensChanged(e) {
return this._visibleLines.onTokensChanged(e);
}
onZonesChanged(e) {
this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
return this._visibleLines.onZonesChanged(e);
}
onThemeChanged(e) {
return this._onOptionsMaybeChanged();
}
// ---- end view event handlers
// ----------- HELPERS FOR OTHERS
getPositionFromDOMInfo(spanNode, offset) {
const viewLineDomNode = this._getViewLineDomNode(spanNode);
if (viewLineDomNode === null) {
// Couldn't find view line node
return null;
}
const lineNumber = this._getLineNumberFor(viewLineDomNode);
if (lineNumber === -1) {
// Couldn't find view line node
return null;
}
if (lineNumber < 1 || lineNumber > this._context.viewModel.getLineCount()) {
// lineNumber is outside range
return null;
}
if (this._context.viewModel.getLineMaxColumn(lineNumber) === 1) {
// Line is empty
return new Position(lineNumber, 1);
}
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return null;
}
let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(spanNode, offset);
const minColumn = this._context.viewModel.getLineMinColumn(lineNumber);
if (column < minColumn) {
column = minColumn;
}
return new Position(lineNumber, column);
}
_getViewLineDomNode(node) {
while (node && node.nodeType === 1) {
if (node.className === ViewLine.CLASS_NAME) {
return node;
}
node = node.parentElement;
}
return null;
}
/**
* @returns the line number of this view line dom node.
*/
_getLineNumberFor(domNode) {
const startLineNumber = this._visibleLines.getStartLineNumber();
const endLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const line = this._visibleLines.getVisibleLine(lineNumber);
if (domNode === line.getDomNode()) {
return lineNumber;
}
}
return -1;
}
getLineWidth(lineNumber) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
// Couldn't find line
return -1;
}
const context = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
const result = this._visibleLines.getVisibleLine(lineNumber).getWidth(context);
this._updateLineWidthsSlowIfDomDidLayout(context);
return result;
}
linesVisibleRangesForRange(_range, includeNewLines) {
if (this.shouldRender()) {
// Cannot read from the DOM because it is dirty
// i.e. the model & the dom are out of sync, so I'd be reading something stale
return null;
}
const originalEndLineNumber = _range.endLineNumber;
const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange());
if (!range) {
return null;
}
const visibleRanges = [];
let visibleRangesLen = 0;
const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
let nextLineModelLineNumber = 0;
if (includeNewLines) {
nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
}
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
continue;
}
const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
const continuesInNextLine = lineNumber !== range.endLineNumber;
const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn;
const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
if (!visibleRangesForLine) {
continue;
}
if (includeNewLines && lineNumber < originalEndLineNumber) {
const currentLineModelLineNumber = nextLineModelLineNumber;
nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;
if (currentLineModelLineNumber !== nextLineModelLineNumber) {
visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += this._typicalHalfwidthCharacterWidth;
}
}
visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine);
}
this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);
if (visibleRangesLen === 0) {
return null;
}
return visibleRanges;
}
_visibleRangesForLineRange(lineNumber, startColumn, endColumn) {
if (this.shouldRender()) {
// Cannot read from the DOM because it is dirty
// i.e. the model & the dom are out of sync, so I'd be reading something stale
return null;
}
if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {
return null;
}
const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
const result = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);
return result;
}
visibleRangeForPosition(position) {
const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);
if (!visibleRanges) {
return null;
}
return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);
}
/**
* Updates the max line width if it is fast to compute.
* Returns true if all lines were taken into account.
* Returns false if some lines need to be reevaluated (in a slow fashion).
*/
_updateLineWidthsFast() {
return this._updateLineWidths(true);
}
_updateLineWidthsSlow() {
this._updateLineWidths(false);
}
/**
* Update the line widths using DOM layout information after someone else
* has caused a synchronous layout.
*/
_updateLineWidthsSlowIfDomDidLayout(domReadingContext) {
if (!domReadingContext.didDomLayout) {
// only proceed if we just did a layout
return;
}
if (this._asyncUpdateLineWidths.isScheduled()) {
// reading widths is not scheduled => widths are up-to-date
return;
}
this._asyncUpdateLineWidths.cancel();
this._updateLineWidthsSlow();
}
_updateLineWidths(fast) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
let localMaxLineWidth = 1;
let allWidthsComputed = true;
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (fast && !visibleLine.getWidthIsFast()) {
// Cannot compute width in a fast way for this line
allWidthsComputed = false;
continue;
}
localMaxLineWidth = Math.max(localMaxLineWidth, visibleLine.getWidth(null));
}
if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.viewModel.getLineCount()) {
// we know the max line width for all the lines
this._maxLineWidth = 0;
}
this._ensureMaxLineWidth(localMaxLineWidth);
return allWidthsComputed;
}
_checkMonospaceFontAssumptions() {
// Problems with monospace assumptions are more apparent for longer lines,
// as small rounding errors start to sum up, so we will select the longest
// line for a closer inspection
let longestLineNumber = -1;
let longestWidth = -1;
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (visibleLine.needsMonospaceFontCheck()) {
const lineWidth = visibleLine.getWidth(null);
if (lineWidth > longestWidth) {
longestWidth = lineWidth;
longestLineNumber = lineNumber;
}
}
}
if (longestLineNumber === -1) {
return;
}
if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) {
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
visibleLine.onMonospaceAssumptionsInvalidated();
}
}
}
prepareRender() {
throw new Error('Not supported');
}
render() {
throw new Error('Not supported');
}
renderText(viewportData) {
// (1) render lines - ensures lines are in the DOM
this._visibleLines.renderLines(viewportData);
this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);
this.domNode.setWidth(this._context.viewLayout.getScrollWidth());
this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));
// (2) compute horizontal scroll position:
// - this must happen after the lines are in the DOM since it might need a line that rendered just now
// - it might change `scrollWidth` and `scrollLeft`
if (this._horizontalRevealRequest) {
const horizontalRevealRequest = this._horizontalRevealRequest;
// Check that we have the line that contains the horizontal range in the viewport
if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) {
this._horizontalRevealRequest = null;
// allow `visibleRangesForRange2` to work
this.onDidRender();
// compute new scroll position
const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest);
if (newScrollLeft) {
if (!this._isViewportWrapping) {
// ensure `scrollWidth` is large enough
this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);
}
// set `scrollLeft`
this._context.viewModel.viewLayout.setScrollPosition({
scrollLeft: newScrollLeft.scrollLeft
}, horizontalRevealRequest.scrollType);
}
}
}
// Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)
if (!this._updateLineWidthsFast()) {
// Computing the width of some lines would be slow => delay it
this._asyncUpdateLineWidths.schedule();
}
else {
this._asyncUpdateLineWidths.cancel();
}
if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) {
const rendStartLineNumber = this._visibleLines.getStartLineNumber();
const rendEndLineNumber = this._visibleLines.getEndLineNumber();
for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
if (visibleLine.needsMonospaceFontCheck()) {
this._asyncCheckMonospaceFontAssumptions.schedule();
break;
}
}
}
// (3) handle scrolling
this._linesContent.setLayerHinting(this._canUseLayerHinting);
this._linesContent.setContain('strict');
const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
this._linesContent.setTop(-adjustedScrollTop);
this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
}
// --- width
_ensureMaxLineWidth(lineWidth) {
const iLineWidth = Math.ceil(lineWidth);
if (this._maxLineWidth < iLineWidth) {
this._maxLineWidth = iLineWidth;
this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
}
}
_computeScrollTopToRevealRange(viewport, source, minimalReveal, range, selections, verticalType) {
const viewportStartY = viewport.top;
const viewportHeight = viewport.height;
const viewportEndY = viewportStartY + viewportHeight;
let boxIsSingleRange;
let boxStartY;
let boxEndY;
if (selections && selections.length > 0) {
let minLineNumber = selections[0].startLineNumber;
let maxLineNumber = selections[0].endLineNumber;
for (let i = 1, len = selections.length; i < len; i++) {
const selection = selections[i];
minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
}
boxIsSingleRange = false;
boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber);
boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight;
}
else if (range) {
boxIsSingleRange = true;
boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);
boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;
}
else {
return -1;
}
const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default';
let paddingTop = 0;
let paddingBottom = 0;
if (!shouldIgnoreScrollOff) {
const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines);
if (this._stickyScrollEnabled) {
paddingTop = Math.max(context, this._maxNumberStickyLines) * this._lineHeight;
}
else {
paddingTop = context * this._lineHeight;
}
paddingBottom = Math.max(0, (context - 1)) * this._lineHeight;
}
else {
if (!minimalReveal) {
// Reveal one more line above (this case is hit when dragging)
paddingTop = this._lineHeight;
}
}
if (!minimalReveal) {
if (verticalType === 0 /* viewEvents.VerticalRevealType.Simple */ || verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */) {
// Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom
paddingBottom += this._lineHeight;
}
}
boxStartY -= paddingTop;
boxEndY += paddingBottom;
let newScrollTop;
if (boxEndY - boxStartY > viewportHeight) {
// the box is larger than the viewport ... scroll to its top
if (!boxIsSingleRange) {
// do not reveal multiple cursors if there are more than fit the viewport
return -1;
}
newScrollTop = boxStartY;
}
else if (verticalType === 5 /* viewEvents.VerticalRevealType.NearTop */ || verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */) {
if (verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
newScrollTop = viewportStartY;
}
else {
// We want a gap that is 20% of the viewport, but with a minimum of 5 lines
const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
// Try to scroll just above the box with the desired gap
const desiredScrollTop = boxStartY - desiredGapAbove;
// But ensure that the box is not pushed out of viewport
const minScrollTop = boxEndY - viewportHeight;
newScrollTop = Math.max(minScrollTop, desiredScrollTop);
}
}
else if (verticalType === 1 /* viewEvents.VerticalRevealType.Center */ || verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */) {
if (verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
newScrollTop = viewportStartY;
}
else {
// Box is outside the viewport... center it
const boxMiddleY = (boxStartY + boxEndY) / 2;
newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);
}
}
else {
newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === 3 /* viewEvents.VerticalRevealType.Top */, verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */);
}
return newScrollTop;
}
_computeScrollLeftToReveal(horizontalRevealRequest) {
const viewport = this._context.viewLayout.getCurrentViewport();
const layoutInfo = this._context.configuration.options.get(146 /* EditorOption.layoutInfo */);
const viewportStartX = viewport.left;
const viewportEndX = viewportStartX + viewport.width - layoutInfo.verticalScrollbarWidth;
let boxStartX = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
let boxEndX = 0;
if (horizontalRevealRequest.type === 'range') {
const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn);
if (!visibleRanges) {
return null;
}
for (const visibleRange of visibleRanges.ranges) {
boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
}
}
else {
for (const selection of horizontalRevealRequest.selections) {
if (selection.startLineNumber !== selection.endLineNumber) {
return null;
}
const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn);
if (!visibleRanges) {
return null;
}
for (const visibleRange of visibleRanges.ranges) {
boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
}
}
}
if (!horizontalRevealRequest.minimalReveal) {
boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);
boxEndX += this._revealHorizontalRightPadding;
}
if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) {
return null;
}
const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);
return {
scrollLeft: newScrollLeft,
maxHorizontalOffset: boxEndX
};
}
_computeMinimumScrolling(viewportStart, viewportEnd, boxStart, boxEnd, revealAtStart, revealAtEnd) {
viewportStart = viewportStart | 0;
viewportEnd = viewportEnd | 0;
boxStart = boxStart | 0;
boxEnd = boxEnd | 0;
revealAtStart = !!revealAtStart;
revealAtEnd = !!revealAtEnd;
const viewportLength = viewportEnd - viewportStart;
const boxLength = boxEnd - boxStart;
if (boxLength < viewportLength) {
// The box would fit in the viewport
if (revealAtStart) {
return boxStart;
}
if (revealAtEnd) {
return Math.max(0, boxEnd - viewportLength);
}
if (boxStart < viewportStart) {
// The box is above the viewport
return boxStart;
}
else if (boxEnd > viewportEnd) {
// The box is below the viewport
return Math.max(0, boxEnd - viewportLength);
}
}
else {
// The box would not fit in the viewport
// Reveal the beginning of the box
return boxStart;
}
return viewportStart;
}
}
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .lines-decorations {
position: absolute;
top: 0;
background: white;
}
/*
Keeping name short for faster parsing.
cldr = core lines decorations rendering (div)
*/
.monaco-editor .margin-view-overlays .cldr {
position: absolute;
height: 100%;
}
@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './linesDecorations.css';
import { DecorationToRender, DedupOverlay } from '../glyphMargin/glyphMargin.js';
export class LinesDecorationsOverlay extends DedupOverlay {
constructor(context) {
super();
this._context = context;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._decorationsLeft = layoutInfo.decorationsLeft;
this._decorationsWidth = layoutInfo.decorationsWidth;
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._decorationsLeft = layoutInfo.decorationsLeft;
this._decorationsWidth = layoutInfo.decorationsWidth;
return true;
}
onDecorationsChanged(e) {
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
_getDecorations(ctx) {
const decorations = ctx.getDecorationsInViewport();
const r = [];
let rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
const d = decorations[i];
const linesDecorationsClassName = d.options.linesDecorationsClassName;
const zIndex = d.options.zIndex;
if (linesDecorationsClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName, d.options.linesDecorationsTooltip ?? null, zIndex);
}
const firstLineDecorationClassName = d.options.firstLineDecorationClassName;
if (firstLineDecorationClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.startLineNumber, firstLineDecorationClassName, d.options.linesDecorationsTooltip ?? null, zIndex);
}
}
return r;
}
prepareRender(ctx) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
const left = this._decorationsLeft.toString();
const width = this._decorationsWidth.toString();
const common = '" style="left:' + left + 'px;width:' + width + 'px;"></div>';
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const decorations = toRender[lineIndex].getDecorations();
let lineOutput = '';
for (const decoration of decorations) {
let addition = '<div class="cldr ' + decoration.className;
if (decoration.tooltip !== null) {
addition += '" title="' + decoration.tooltip; // The tooltip is already escaped.
}
addition += common;
lineOutput += addition;
}
output[lineIndex] = lineOutput;
}
this._renderResult = output;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
return this._renderResult[lineNumber - startLineNumber];
}
}
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .margin {
background-color: var(--vscode-editorGutter-background);
}
@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './margin.css';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { ViewPart } from '../../view/viewPart.js';
export class Margin extends ViewPart {
static { this.CLASS_NAME = 'glyph-margin'; }
static { this.OUTER_CLASS_NAME = 'margin'; }
constructor(context) {
super(context);
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
this._contentLeft = layoutInfo.contentLeft;
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setClassName(Margin.OUTER_CLASS_NAME);
this._domNode.setPosition('absolute');
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div'));
this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME);
this._domNode.appendChild(this._glyphMarginBackgroundDomNode);
}
dispose() {
super.dispose();
}
getDomNode() {
return this._domNode;
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._canUseLayerHinting = !options.get(32 /* EditorOption.disableLayerHinting */);
this._contentLeft = layoutInfo.contentLeft;
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
return true;
}
onScrollChanged(e) {
return super.onScrollChanged(e) || e.scrollTopChanged;
}
// --- end event handlers
prepareRender(ctx) {
// Nothing to read
}
render(ctx) {
this._domNode.setLayerHinting(this._canUseLayerHinting);
this._domNode.setContain('strict');
const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta;
this._domNode.setTop(-adjustedScrollTop);
const height = Math.min(ctx.scrollHeight, 1000000);
this._domNode.setHeight(height);
this._domNode.setWidth(this._contentLeft);
this._glyphMarginBackgroundDomNode.setLeft(this._glyphMarginLeft);
this._glyphMarginBackgroundDomNode.setWidth(this._glyphMarginWidth);
this._glyphMarginBackgroundDomNode.setHeight(height);
}
}
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cmdr = core margin decorations rendering (div)
*/
.monaco-editor .margin-view-overlays .cmdr {
position: absolute;
left: 0;
width: 100%;
height: 100%;
}
@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './marginDecorations.css';
import { DecorationToRender, DedupOverlay } from '../glyphMargin/glyphMargin.js';
export class MarginViewLineDecorationsOverlay extends DedupOverlay {
constructor(context) {
super();
this._context = context;
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
return true;
}
onDecorationsChanged(e) {
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
_getDecorations(ctx) {
const decorations = ctx.getDecorationsInViewport();
const r = [];
let rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
const d = decorations[i];
const marginClassName = d.options.marginClassName;
const zIndex = d.options.zIndex;
if (marginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, marginClassName, null, zIndex);
}
}
return r;
}
prepareRender(ctx) {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const decorations = toRender[lineIndex].getDecorations();
let lineOutput = '';
for (const decoration of decorations) {
lineOutput += '<div class="cmdr ' + decoration.className + '" style=""></div>';
}
output[lineIndex] = lineOutput;
}
this._renderResult = output;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
return this._renderResult[lineNumber - startLineNumber];
}
}
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* START cover the case that slider is visible on mouseover */
.monaco-editor .minimap.slider-mouseover .minimap-slider {
opacity: 0;
transition: opacity 100ms linear;
}
.monaco-editor .minimap.slider-mouseover:hover .minimap-slider {
opacity: 1;
}
.monaco-editor .minimap.slider-mouseover .minimap-slider.active {
opacity: 1;
}
/* END cover the case that slider is visible on mouseover */
.monaco-editor .minimap-slider .minimap-slider-horizontal {
background: var(--vscode-minimapSlider-background);
}
.monaco-editor .minimap-slider:hover .minimap-slider-horizontal {
background: var(--vscode-minimapSlider-hoverBackground);
}
.monaco-editor .minimap-slider.active .minimap-slider-horizontal {
background: var(--vscode-minimapSlider-activeBackground);
}
.monaco-editor .minimap-shadow-visible {
box-shadow: var(--vscode-scrollbar-shadow) -6px 0 6px -6px inset;
}
.monaco-editor .minimap-shadow-hidden {
position: absolute;
width: 0;
}
.monaco-editor .minimap-shadow-visible {
position: absolute;
left: -6px;
width: 6px;
}
.monaco-editor.no-minimap-shadow .minimap-shadow-visible {
position: absolute;
left: -1px;
width: 1px;
}
/* 0.5s fade in/out for the minimap */
.minimap.autohide {
opacity: 0;
transition: opacity 0.5s;
}
.minimap.autohide:hover {
opacity: 1;
}
.monaco-editor .minimap {
z-index: 5;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getCharIndex } from './minimapCharSheet.js';
import { toUint8 } from '../../../../base/common/uint.js';
export class MinimapCharRenderer {
constructor(charData, scale) {
this.scale = scale;
this._minimapCharRendererBrand = undefined;
this.charDataNormal = MinimapCharRenderer.soften(charData, 12 / 15);
this.charDataLight = MinimapCharRenderer.soften(charData, 50 / 60);
}
static soften(input, ratio) {
const result = new Uint8ClampedArray(input.length);
for (let i = 0, len = input.length; i < len; i++) {
result[i] = toUint8(input[i] * ratio);
}
return result;
}
renderChar(target, dx, dy, chCode, color, foregroundAlpha, backgroundColor, backgroundAlpha, fontScale, useLighterFont, force1pxHeight) {
const charWidth = 1 /* Constants.BASE_CHAR_WIDTH */ * this.scale;
const charHeight = 2 /* Constants.BASE_CHAR_HEIGHT */ * this.scale;
const renderHeight = (force1pxHeight ? 1 : charHeight);
if (dx + charWidth > target.width || dy + renderHeight > target.height) {
console.warn('bad render request outside image data');
return;
}
const charData = useLighterFont ? this.charDataLight : this.charDataNormal;
const charIndex = getCharIndex(chCode, fontScale);
const destWidth = target.width * 4 /* Constants.RGBA_CHANNELS_CNT */;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const destAlpha = Math.max(foregroundAlpha, backgroundAlpha);
const dest = target.data;
let sourceOffset = charIndex * charWidth * charHeight;
let row = dy * destWidth + dx * 4 /* Constants.RGBA_CHANNELS_CNT */;
for (let y = 0; y < renderHeight; y++) {
let column = row;
for (let x = 0; x < charWidth; x++) {
const c = (charData[sourceOffset++] / 255) * (foregroundAlpha / 255);
dest[column++] = backgroundR + deltaR * c;
dest[column++] = backgroundG + deltaG * c;
dest[column++] = backgroundB + deltaB * c;
dest[column++] = destAlpha;
}
row += destWidth;
}
}
blockRenderChar(target, dx, dy, color, foregroundAlpha, backgroundColor, backgroundAlpha, force1pxHeight) {
const charWidth = 1 /* Constants.BASE_CHAR_WIDTH */ * this.scale;
const charHeight = 2 /* Constants.BASE_CHAR_HEIGHT */ * this.scale;
const renderHeight = (force1pxHeight ? 1 : charHeight);
if (dx + charWidth > target.width || dy + renderHeight > target.height) {
console.warn('bad render request outside image data');
return;
}
const destWidth = target.width * 4 /* Constants.RGBA_CHANNELS_CNT */;
const c = 0.5 * (foregroundAlpha / 255);
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const colorR = backgroundR + deltaR * c;
const colorG = backgroundG + deltaG * c;
const colorB = backgroundB + deltaB * c;
const destAlpha = Math.max(foregroundAlpha, backgroundAlpha);
const dest = target.data;
let row = dy * destWidth + dx * 4 /* Constants.RGBA_CHANNELS_CNT */;
for (let y = 0; y < renderHeight; y++) {
let column = row;
for (let x = 0; x < charWidth; x++) {
dest[column++] = colorR;
dest[column++] = colorG;
dest[column++] = colorB;
dest[column++] = destAlpha;
}
row += destWidth;
}
}
}
@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MinimapCharRenderer } from './minimapCharRenderer.js';
import { allCharCodes } from './minimapCharSheet.js';
import { prebakedMiniMaps } from './minimapPreBaked.js';
import { toUint8 } from '../../../../base/common/uint.js';
/**
* Creates character renderers. It takes a 'scale' that determines how large
* characters should be drawn. Using this, it draws data into a canvas and
* then downsamples the characters as necessary for the current display.
* This makes rendering more efficient, rather than drawing a full (tiny)
* font, or downsampling in real-time.
*/
export class MinimapCharRendererFactory {
/**
* Creates a new character renderer factory with the given scale.
*/
static create(scale, fontFamily) {
// renderers are immutable. By default we'll 'create' a new minimap
// character renderer whenever we switch editors, no need to do extra work.
if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) {
return this.lastCreated;
}
let factory;
if (prebakedMiniMaps[scale]) {
factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale);
}
else {
factory = MinimapCharRendererFactory.createFromSampleData(MinimapCharRendererFactory.createSampleData(fontFamily).data, scale);
}
this.lastFontFamily = fontFamily;
this.lastCreated = factory;
return factory;
}
/**
* Creates the font sample data, writing to a canvas.
*/
static createSampleData(fontFamily) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.style.height = `${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px`;
canvas.height = 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
canvas.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
canvas.style.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ + 'px';
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px ${fontFamily}`;
ctx.textBaseline = 'middle';
let x = 0;
for (const code of allCharCodes) {
ctx.fillText(String.fromCharCode(code), x, 16 /* Constants.SAMPLED_CHAR_HEIGHT */ / 2);
x += 10 /* Constants.SAMPLED_CHAR_WIDTH */;
}
return ctx.getImageData(0, 0, 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */, 16 /* Constants.SAMPLED_CHAR_HEIGHT */);
}
/**
* Creates a character renderer from the canvas sample data.
*/
static createFromSampleData(source, scale) {
const expectedLength = 16 /* Constants.SAMPLED_CHAR_HEIGHT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */ * 96 /* Constants.CHAR_COUNT */;
if (source.length !== expectedLength) {
throw new Error('Unexpected source in MinimapCharRenderer');
}
const charData = MinimapCharRendererFactory._downsample(source, scale);
return new MinimapCharRenderer(charData, scale);
}
static _downsampleChar(source, sourceOffset, dest, destOffset, scale) {
const width = 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
const height = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale;
let targetIndex = destOffset;
let brightest = 0;
// This is essentially an ad-hoc rescaling algorithm. Standard approaches
// like bicubic interpolation are awesome for scaling between image sizes,
// but don't work so well when scaling to very small pixel values, we end
// up with blurry, indistinct forms.
//
// The approach taken here is simply mapping each source pixel to the target
// pixels, and taking the weighted values for all pixels in each, and then
// averaging them out. Finally we apply an intensity boost in _downsample,
// since when scaling to the smallest pixel sizes there's more black space
// which causes characters to be much less distinct.
for (let y = 0; y < height; y++) {
// 1. For this destination pixel, get the source pixels we're sampling
// from (x1, y1) to the next pixel (x2, y2)
const sourceY1 = (y / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
const sourceY2 = ((y + 1) / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
for (let x = 0; x < width; x++) {
const sourceX1 = (x / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
const sourceX2 = ((x + 1) / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
// 2. Sample all of them, summing them up and weighting them. Similar
// to bilinear interpolation.
let value = 0;
let samples = 0;
for (let sy = sourceY1; sy < sourceY2; sy++) {
const sourceRow = sourceOffset + Math.floor(sy) * 3840 /* Constants.RGBA_SAMPLED_ROW_WIDTH */;
const yBalance = 1 - (sy - Math.floor(sy));
for (let sx = sourceX1; sx < sourceX2; sx++) {
const xBalance = 1 - (sx - Math.floor(sx));
const sourceIndex = sourceRow + Math.floor(sx) * 4 /* Constants.RGBA_CHANNELS_CNT */;
const weight = xBalance * yBalance;
samples += weight;
value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight;
}
}
const final = value / samples;
brightest = Math.max(brightest, final);
dest[targetIndex++] = toUint8(final);
}
}
return brightest;
}
static _downsample(data, scale) {
const pixelsPerCharacter = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale * 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
const resultLen = pixelsPerCharacter * 96 /* Constants.CHAR_COUNT */;
const result = new Uint8ClampedArray(resultLen);
let resultOffset = 0;
let sourceOffset = 0;
let brightest = 0;
for (let charIndex = 0; charIndex < 96 /* Constants.CHAR_COUNT */; charIndex++) {
brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale));
resultOffset += pixelsPerCharacter;
sourceOffset += 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */;
}
if (brightest > 0) {
const adjust = 255 / brightest;
for (let i = 0; i < resultLen; i++) {
result[i] *= adjust;
}
}
return result;
}
}
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const allCharCodes = (() => {
const v = [];
for (let i = 32 /* Constants.START_CH_CODE */; i <= 126 /* Constants.END_CH_CODE */; i++) {
v.push(i);
}
v.push(65533 /* Constants.UNKNOWN_CODE */);
return v;
})();
export const getCharIndex = (chCode, fontScale) => {
chCode -= 32 /* Constants.START_CH_CODE */;
if (chCode < 0 || chCode > 96 /* Constants.CHAR_COUNT */) {
if (fontScale <= 2) {
// for smaller scales, we can get away with using any ASCII character...
return (chCode + 96 /* Constants.CHAR_COUNT */) % 96 /* Constants.CHAR_COUNT */;
}
return 96 /* Constants.CHAR_COUNT */ - 1; // unknown symbol
}
return chCode;
};
@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createSingleCallFunction } from '../../../../base/common/functional.js';
const charTable = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
A: 10,
B: 11,
C: 12,
D: 13,
E: 14,
F: 15
};
const decodeData = (str) => {
const output = new Uint8ClampedArray(str.length / 2);
for (let i = 0; i < str.length; i += 2) {
output[i >> 1] = (charTable[str[i]] << 4) | (charTable[str[i + 1]] & 0xF);
}
return output;
};
/*
const encodeData = (data: Uint8ClampedArray, length: string) => {
const chars = '0123456789ABCDEF';
let output = '';
for (let i = 0; i < data.length; i++) {
output += chars[data[i] >> 4] + chars[data[i] & 0xf];
}
return output;
};
*/
/**
* Map of minimap scales to prebaked sample data at those scales. We don't
* sample much larger data, because then font family becomes visible, which
* is use-configurable.
*/
export const prebakedMiniMaps = {
1: createSingleCallFunction(() => decodeData('0000511D6300CF609C709645A78432005642574171487021003C451900274D35D762755E8B629C5BA856AF57BA649530C167D1512A272A3F6038604460398526BCA2A968DB6F8957C768BE5FBE2FB467CF5D8D5B795DC7625B5DFF50DE64C466DB2FC47CD860A65E9A2EB96CB54CE06DA763AB2EA26860524D3763536601005116008177A8705E53AB738E6A982F88BAA35B5F5B626D9C636B449B737E5B7B678598869A662F6B5B8542706C704C80736A607578685B70594A49715A4522E792')),
2: createSingleCallFunction(() => decodeData('000000000000000055394F383D2800008B8B1F210002000081B1CBCBCC820000847AAF6B9AAF2119BE08B8881AD60000A44FD07DCCF107015338130C00000000385972265F390B406E2437634B4B48031B12B8A0847000001E15B29A402F0000000000004B33460B00007A752C2A0000000000004D3900000084394B82013400ABA5CFC7AD9C0302A45A3E5A98AB000089A43382D97900008BA54AA087A70A0248A6A7AE6DBE0000BF6F94987EA40A01A06DCFA7A7A9030496C32F77891D0000A99FB1A0AFA80603B29AB9CA75930D010C0948354D3900000C0948354F37460D0028BE673D8400000000AF9D7B6E00002B007AA8933400007AA642675C2700007984CFB9C3985B768772A8A6B7B20000CAAECAAFC4B700009F94A6009F840009D09F9BA4CA9C0000CC8FC76DC87F0000C991C472A2000000A894A48CA7B501079BA2C9C69BA20000B19A5D3FA89000005CA6009DA2960901B0A7F0669FB200009D009E00B7890000DAD0F5D092820000D294D4C48BD10000B5A7A4A3B1A50402CAB6CBA6A2000000B5A7A4A3B1A8044FCDADD19D9CB00000B7778F7B8AAE0803C9AB5D3F5D3F00009EA09EA0BAB006039EA0989A8C7900009B9EF4D6B7C00000A9A7816CACA80000ABAC84705D3F000096DA635CDC8C00006F486F266F263D4784006124097B00374F6D2D6D2D6D4A3A95872322000000030000000000008D8939130000000000002E22A5C9CBC70600AB25C0B5C9B400061A2DB04CA67001082AA6BEBEBFC606002321DACBC19E03087AA08B6768380000282FBAC0B8CA7A88AD25BBA5A29900004C396C5894A6000040485A6E356E9442A32CD17EADA70000B4237923628600003E2DE9C1D7B500002F25BBA5A2990000231DB6AFB4A804023025C0B5CAB588062B2CBDBEC0C706882435A75CA20000002326BD6A82A908048B4B9A5A668000002423A09CB4BB060025259C9D8A7900001C1FCAB2C7C700002A2A9387ABA200002626A4A47D6E9D14333163A0C87500004B6F9C2D643A257049364936493647358A34438355497F1A0000A24C1D590000D38DFFBDD4CD3126'))
};
@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .overlayWidgets {
position: absolute;
top: 0;
left:0;
}
@@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './overlayWidgets.css';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
import * as dom from '../../../../base/browser/dom.js';
export class ViewOverlayWidgets extends ViewPart {
constructor(context, viewDomNode) {
super(context);
this._viewDomNode = viewDomNode;
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._widgets = {};
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
this._minimapWidth = layoutInfo.minimap.minimapWidth;
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
this._editorHeight = layoutInfo.height;
this._editorWidth = layoutInfo.width;
this._viewDomNodeRect = { top: 0, left: 0, width: 0, height: 0 };
this._domNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this._domNode, 4 /* PartFingerprint.OverlayWidgets */);
this._domNode.setClassName('overlayWidgets');
this.overflowingOverlayWidgetsDomNode = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.overflowingOverlayWidgetsDomNode, 5 /* PartFingerprint.OverflowingOverlayWidgets */);
this.overflowingOverlayWidgetsDomNode.setClassName('overflowingOverlayWidgets');
}
dispose() {
super.dispose();
this._widgets = {};
}
getDomNode() {
return this._domNode;
}
// ---- begin view event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
this._minimapWidth = layoutInfo.minimap.minimapWidth;
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
this._editorHeight = layoutInfo.height;
this._editorWidth = layoutInfo.width;
return true;
}
// ---- end view event handlers
addWidget(widget) {
const domNode = createFastDomNode(widget.getDomNode());
this._widgets[widget.getId()] = {
widget: widget,
preference: null,
domNode: domNode
};
// This is sync because a widget wants to be in the dom
domNode.setPosition('absolute');
domNode.setAttribute('widgetId', widget.getId());
if (widget.allowEditorOverflow) {
this.overflowingOverlayWidgetsDomNode.appendChild(domNode);
}
else {
this._domNode.appendChild(domNode);
}
this.setShouldRender();
this._updateMaxMinWidth();
}
setWidgetPosition(widget, position) {
const widgetData = this._widgets[widget.getId()];
const preference = position ? position.preference : null;
const stack = position?.stackOridinal;
if (widgetData.preference === preference && widgetData.stack === stack) {
this._updateMaxMinWidth();
return false;
}
widgetData.preference = preference;
widgetData.stack = stack;
this.setShouldRender();
this._updateMaxMinWidth();
return true;
}
removeWidget(widget) {
const widgetId = widget.getId();
if (this._widgets.hasOwnProperty(widgetId)) {
const widgetData = this._widgets[widgetId];
const domNode = widgetData.domNode.domNode;
delete this._widgets[widgetId];
domNode.remove();
this.setShouldRender();
this._updateMaxMinWidth();
}
}
_updateMaxMinWidth() {
let maxMinWidth = 0;
const keys = Object.keys(this._widgets);
for (let i = 0, len = keys.length; i < len; i++) {
const widgetId = keys[i];
const widget = this._widgets[widgetId];
const widgetMinWidthInPx = widget.widget.getMinContentWidthInPx?.();
if (typeof widgetMinWidthInPx !== 'undefined') {
maxMinWidth = Math.max(maxMinWidth, widgetMinWidthInPx);
}
}
this._context.viewLayout.setOverlayWidgetsMinWidth(maxMinWidth);
}
_renderWidget(widgetData, stackCoordinates) {
const domNode = widgetData.domNode;
if (widgetData.preference === null) {
domNode.setTop('');
return;
}
const maxRight = (2 * this._verticalScrollbarWidth) + this._minimapWidth;
if (widgetData.preference === 0 /* OverlayWidgetPositionPreference.TOP_RIGHT_CORNER */ || widgetData.preference === 1 /* OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER */) {
if (widgetData.preference === 1 /* OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER */) {
const widgetHeight = domNode.domNode.clientHeight;
domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));
}
else {
domNode.setTop(0);
}
if (widgetData.stack !== undefined) {
domNode.setTop(stackCoordinates[widgetData.preference]);
stackCoordinates[widgetData.preference] += domNode.domNode.clientWidth;
}
else {
domNode.setRight(maxRight);
}
}
else if (widgetData.preference === 2 /* OverlayWidgetPositionPreference.TOP_CENTER */) {
domNode.domNode.style.right = '50%';
if (widgetData.stack !== undefined) {
domNode.setTop(stackCoordinates[2 /* OverlayWidgetPositionPreference.TOP_CENTER */]);
stackCoordinates[2 /* OverlayWidgetPositionPreference.TOP_CENTER */] += domNode.domNode.clientHeight;
}
else {
domNode.setTop(0);
}
}
else {
const { top, left } = widgetData.preference;
const fixedOverflowWidgets = this._context.configuration.options.get(42 /* EditorOption.fixedOverflowWidgets */);
if (fixedOverflowWidgets && widgetData.widget.allowEditorOverflow) {
// top, left are computed relative to the editor and we need them relative to the page
const editorBoundingBox = this._viewDomNodeRect;
domNode.setTop(top + editorBoundingBox.top);
domNode.setLeft(left + editorBoundingBox.left);
domNode.setPosition('fixed');
}
else {
domNode.setTop(top);
domNode.setLeft(left);
domNode.setPosition('absolute');
}
}
}
prepareRender(ctx) {
this._viewDomNodeRect = dom.getDomNodePagePosition(this._viewDomNode.domNode);
}
render(ctx) {
this._domNode.setWidth(this._editorWidth);
const keys = Object.keys(this._widgets);
const stackCoordinates = Array.from({ length: 2 /* OverlayWidgetPositionPreference.TOP_CENTER */ + 1 }, () => 0);
keys.sort((a, b) => (this._widgets[a].stack || 0) - (this._widgets[b].stack || 0));
for (let i = 0, len = keys.length; i < len; i++) {
const widgetId = keys[i];
this._renderWidget(this._widgets[widgetId], stackCoordinates);
}
}
}
@@ -0,0 +1,413 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { Color } from '../../../../base/common/color.js';
import { ViewPart } from '../../view/viewPart.js';
import { Position } from '../../../common/core/position.js';
import { TokenizationRegistry } from '../../../common/languages.js';
import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground, editorMultiCursorSecondaryForeground, editorMultiCursorPrimaryForeground } from '../../../common/core/editorColorRegistry.js';
import { OverviewRulerDecorationsGroup } from '../../../common/viewModel.js';
import { equals } from '../../../../base/common/arrays.js';
class Settings {
constructor(config, theme) {
const options = config.options;
this.lineHeight = options.get(67 /* EditorOption.lineHeight */);
this.pixelRatio = options.get(144 /* EditorOption.pixelRatio */);
this.overviewRulerLanes = options.get(83 /* EditorOption.overviewRulerLanes */);
this.renderBorder = options.get(82 /* EditorOption.overviewRulerBorder */);
const borderColor = theme.getColor(editorOverviewRulerBorder);
this.borderColor = borderColor ? borderColor.toString() : null;
this.hideCursor = options.get(59 /* EditorOption.hideCursorInOverviewRuler */);
const cursorColorSingle = theme.getColor(editorCursorForeground);
this.cursorColorSingle = cursorColorSingle ? cursorColorSingle.transparent(0.7).toString() : null;
const cursorColorPrimary = theme.getColor(editorMultiCursorPrimaryForeground);
this.cursorColorPrimary = cursorColorPrimary ? cursorColorPrimary.transparent(0.7).toString() : null;
const cursorColorSecondary = theme.getColor(editorMultiCursorSecondaryForeground);
this.cursorColorSecondary = cursorColorSecondary ? cursorColorSecondary.transparent(0.7).toString() : null;
this.themeType = theme.type;
const minimapOpts = options.get(73 /* EditorOption.minimap */);
const minimapEnabled = minimapOpts.enabled;
const minimapSide = minimapOpts.side;
const themeColor = theme.getColor(editorOverviewRulerBackground);
const defaultBackground = TokenizationRegistry.getDefaultBackground();
if (themeColor) {
this.backgroundColor = themeColor;
}
else if (minimapEnabled && minimapSide === 'right') {
this.backgroundColor = defaultBackground;
}
else {
this.backgroundColor = null;
}
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
const position = layoutInfo.overviewRuler;
this.top = position.top;
this.right = position.right;
this.domWidth = position.width;
this.domHeight = position.height;
if (this.overviewRulerLanes === 0) {
// overview ruler is off
this.canvasWidth = 0;
this.canvasHeight = 0;
}
else {
this.canvasWidth = (this.domWidth * this.pixelRatio) | 0;
this.canvasHeight = (this.domHeight * this.pixelRatio) | 0;
}
const [x, w] = this._initLanes(1, this.canvasWidth, this.overviewRulerLanes);
this.x = x;
this.w = w;
}
_initLanes(canvasLeftOffset, canvasWidth, laneCount) {
const remainingWidth = canvasWidth - canvasLeftOffset;
if (laneCount >= 3) {
const leftWidth = Math.floor(remainingWidth / 3);
const rightWidth = Math.floor(remainingWidth / 3);
const centerWidth = remainingWidth - leftWidth - rightWidth;
const leftOffset = canvasLeftOffset;
const centerOffset = leftOffset + leftWidth;
const rightOffset = leftOffset + leftWidth + centerWidth;
return [
[
0,
leftOffset, // Left
centerOffset, // Center
leftOffset, // Left | Center
rightOffset, // Right
leftOffset, // Left | Right
centerOffset, // Center | Right
leftOffset, // Left | Center | Right
], [
0,
leftWidth, // Left
centerWidth, // Center
leftWidth + centerWidth, // Left | Center
rightWidth, // Right
leftWidth + centerWidth + rightWidth, // Left | Right
centerWidth + rightWidth, // Center | Right
leftWidth + centerWidth + rightWidth, // Left | Center | Right
]
];
}
else if (laneCount === 2) {
const leftWidth = Math.floor(remainingWidth / 2);
const rightWidth = remainingWidth - leftWidth;
const leftOffset = canvasLeftOffset;
const rightOffset = leftOffset + leftWidth;
return [
[
0,
leftOffset, // Left
leftOffset, // Center
leftOffset, // Left | Center
rightOffset, // Right
leftOffset, // Left | Right
leftOffset, // Center | Right
leftOffset, // Left | Center | Right
], [
0,
leftWidth, // Left
leftWidth, // Center
leftWidth, // Left | Center
rightWidth, // Right
leftWidth + rightWidth, // Left | Right
leftWidth + rightWidth, // Center | Right
leftWidth + rightWidth, // Left | Center | Right
]
];
}
else {
const offset = canvasLeftOffset;
const width = remainingWidth;
return [
[
0,
offset, // Left
offset, // Center
offset, // Left | Center
offset, // Right
offset, // Left | Right
offset, // Center | Right
offset, // Left | Center | Right
], [
0,
width, // Left
width, // Center
width, // Left | Center
width, // Right
width, // Left | Right
width, // Center | Right
width, // Left | Center | Right
]
];
}
}
equals(other) {
return (this.lineHeight === other.lineHeight
&& this.pixelRatio === other.pixelRatio
&& this.overviewRulerLanes === other.overviewRulerLanes
&& this.renderBorder === other.renderBorder
&& this.borderColor === other.borderColor
&& this.hideCursor === other.hideCursor
&& this.cursorColorSingle === other.cursorColorSingle
&& this.cursorColorPrimary === other.cursorColorPrimary
&& this.cursorColorSecondary === other.cursorColorSecondary
&& this.themeType === other.themeType
&& Color.equals(this.backgroundColor, other.backgroundColor)
&& this.top === other.top
&& this.right === other.right
&& this.domWidth === other.domWidth
&& this.domHeight === other.domHeight
&& this.canvasWidth === other.canvasWidth
&& this.canvasHeight === other.canvasHeight);
}
}
export class DecorationsOverviewRuler extends ViewPart {
constructor(context) {
super(context);
this._actualShouldRender = 0 /* ShouldRenderValue.NotNeeded */;
this._renderedDecorations = [];
this._renderedCursorPositions = [];
this._domNode = createFastDomNode(document.createElement('canvas'));
this._domNode.setClassName('decorationsOverviewRuler');
this._domNode.setPosition('absolute');
this._domNode.setLayerHinting(true);
this._domNode.setContain('strict');
this._domNode.setAttribute('aria-hidden', 'true');
this._updateSettings(false);
this._tokensColorTrackerListener = TokenizationRegistry.onDidChange((e) => {
if (e.changedColorMap) {
this._updateSettings(true);
}
});
this._cursorPositions = [{ position: new Position(1, 1), color: this._settings.cursorColorSingle }];
}
dispose() {
super.dispose();
this._tokensColorTrackerListener.dispose();
}
_updateSettings(renderNow) {
const newSettings = new Settings(this._context.configuration, this._context.theme);
if (this._settings && this._settings.equals(newSettings)) {
// nothing to do
return false;
}
this._settings = newSettings;
this._domNode.setTop(this._settings.top);
this._domNode.setRight(this._settings.right);
this._domNode.setWidth(this._settings.domWidth);
this._domNode.setHeight(this._settings.domHeight);
this._domNode.domNode.width = this._settings.canvasWidth;
this._domNode.domNode.height = this._settings.canvasHeight;
if (renderNow) {
this._render();
}
return true;
}
// ---- begin view event handlers
_markRenderingIsNeeded() {
this._actualShouldRender = 2 /* ShouldRenderValue.Needed */;
return true;
}
_markRenderingIsMaybeNeeded() {
this._actualShouldRender = 1 /* ShouldRenderValue.Maybe */;
return true;
}
onConfigurationChanged(e) {
return this._updateSettings(false) ? this._markRenderingIsNeeded() : false;
}
onCursorStateChanged(e) {
this._cursorPositions = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
let color = this._settings.cursorColorSingle;
if (len > 1) {
color = i === 0 ? this._settings.cursorColorPrimary : this._settings.cursorColorSecondary;
}
this._cursorPositions.push({ position: e.selections[i].getPosition(), color });
}
this._cursorPositions.sort((a, b) => Position.compare(a.position, b.position));
return this._markRenderingIsMaybeNeeded();
}
onDecorationsChanged(e) {
if (e.affectsOverviewRuler) {
return this._markRenderingIsMaybeNeeded();
}
return false;
}
onFlushed(e) {
return this._markRenderingIsNeeded();
}
onScrollChanged(e) {
return e.scrollHeightChanged ? this._markRenderingIsNeeded() : false;
}
onZonesChanged(e) {
return this._markRenderingIsNeeded();
}
onThemeChanged(e) {
return this._updateSettings(false) ? this._markRenderingIsNeeded() : false;
}
// ---- end view event handlers
getDomNode() {
return this._domNode.domNode;
}
prepareRender(ctx) {
// Nothing to read
}
render(editorCtx) {
this._render();
this._actualShouldRender = 0 /* ShouldRenderValue.NotNeeded */;
}
_render() {
const backgroundColor = this._settings.backgroundColor;
if (this._settings.overviewRulerLanes === 0) {
// overview ruler is off
this._domNode.setBackgroundColor(backgroundColor ? Color.Format.CSS.formatHexA(backgroundColor) : '');
this._domNode.setDisplay('none');
return;
}
const decorations = this._context.viewModel.getAllOverviewRulerDecorations(this._context.theme);
decorations.sort(OverviewRulerDecorationsGroup.compareByRenderingProps);
if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */ && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) {
this._actualShouldRender = 2 /* ShouldRenderValue.Needed */;
}
if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */ && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.position.lineNumber === b.position.lineNumber && a.color === b.color)) {
this._actualShouldRender = 2 /* ShouldRenderValue.Needed */;
}
if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */) {
// both decorations and cursor positions are unchanged, nothing to do
return;
}
this._renderedDecorations = decorations;
this._renderedCursorPositions = this._cursorPositions;
this._domNode.setDisplay('block');
const canvasWidth = this._settings.canvasWidth;
const canvasHeight = this._settings.canvasHeight;
const lineHeight = this._settings.lineHeight;
const viewLayout = this._context.viewLayout;
const outerHeight = this._context.viewLayout.getScrollHeight();
const heightRatio = canvasHeight / outerHeight;
const minDecorationHeight = (6 /* Constants.MIN_DECORATION_HEIGHT */ * this._settings.pixelRatio) | 0;
const halfMinDecorationHeight = (minDecorationHeight / 2) | 0;
const canvasCtx = this._domNode.domNode.getContext('2d');
if (backgroundColor) {
if (backgroundColor.isOpaque()) {
// We have a background color which is opaque, we can just paint the entire surface with it
canvasCtx.fillStyle = Color.Format.CSS.formatHexA(backgroundColor);
canvasCtx.fillRect(0, 0, canvasWidth, canvasHeight);
}
else {
// We have a background color which is transparent, we need to first clear the surface and
// then fill it
canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
canvasCtx.fillStyle = Color.Format.CSS.formatHexA(backgroundColor);
canvasCtx.fillRect(0, 0, canvasWidth, canvasHeight);
}
}
else {
// We don't have a background color
canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
}
const x = this._settings.x;
const w = this._settings.w;
for (const decorationGroup of decorations) {
const color = decorationGroup.color;
const decorationGroupData = decorationGroup.data;
canvasCtx.fillStyle = color;
let prevLane = 0;
let prevY1 = 0;
let prevY2 = 0;
for (let i = 0, len = decorationGroupData.length / 3; i < len; i++) {
const lane = decorationGroupData[3 * i];
const startLineNumber = decorationGroupData[3 * i + 1];
const endLineNumber = decorationGroupData[3 * i + 2];
let y1 = (viewLayout.getVerticalOffsetForLineNumber(startLineNumber) * heightRatio) | 0;
let y2 = ((viewLayout.getVerticalOffsetForLineNumber(endLineNumber) + lineHeight) * heightRatio) | 0;
const height = y2 - y1;
if (height < minDecorationHeight) {
let yCenter = ((y1 + y2) / 2) | 0;
if (yCenter < halfMinDecorationHeight) {
yCenter = halfMinDecorationHeight;
}
else if (yCenter + halfMinDecorationHeight > canvasHeight) {
yCenter = canvasHeight - halfMinDecorationHeight;
}
y1 = yCenter - halfMinDecorationHeight;
y2 = yCenter + halfMinDecorationHeight;
}
if (y1 > prevY2 + 1 || lane !== prevLane) {
// flush prev
if (i !== 0) {
canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1);
}
prevLane = lane;
prevY1 = y1;
prevY2 = y2;
}
else {
// merge into prev
if (y2 > prevY2) {
prevY2 = y2;
}
}
}
canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1);
}
// Draw cursors
if (!this._settings.hideCursor) {
const cursorHeight = (2 * this._settings.pixelRatio) | 0;
const halfCursorHeight = (cursorHeight / 2) | 0;
const cursorX = this._settings.x[7 /* OverviewRulerLane.Full */];
const cursorW = this._settings.w[7 /* OverviewRulerLane.Full */];
let prevY1 = -100;
let prevY2 = -100;
let prevColor = null;
for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
const color = this._cursorPositions[i].color;
if (!color) {
continue;
}
const cursor = this._cursorPositions[i].position;
let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0;
if (yCenter < halfCursorHeight) {
yCenter = halfCursorHeight;
}
else if (yCenter + halfCursorHeight > canvasHeight) {
yCenter = canvasHeight - halfCursorHeight;
}
const y1 = yCenter - halfCursorHeight;
const y2 = y1 + cursorHeight;
if (y1 > prevY2 + 1 || color !== prevColor) {
// flush prev
if (i !== 0 && prevColor) {
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
}
prevY1 = y1;
prevY2 = y2;
}
else {
// merge into prev
if (y2 > prevY2) {
prevY2 = y2;
}
}
prevColor = color;
canvasCtx.fillStyle = color;
}
if (prevColor) {
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
}
}
if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) {
canvasCtx.beginPath();
canvasCtx.lineWidth = 1;
canvasCtx.strokeStyle = this._settings.borderColor;
canvasCtx.moveTo(0, 0);
canvasCtx.lineTo(0, canvasHeight);
canvasCtx.moveTo(1, 0);
canvasCtx.lineTo(canvasWidth, 0);
canvasCtx.stroke();
}
}
}
@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { OverviewZoneManager } from '../../../common/viewModel/overviewZoneManager.js';
import { ViewEventHandler } from '../../../common/viewEventHandler.js';
export class OverviewRuler extends ViewEventHandler {
constructor(context, cssClassName) {
super();
this._context = context;
const options = this._context.configuration.options;
this._domNode = createFastDomNode(document.createElement('canvas'));
this._domNode.setClassName(cssClassName);
this._domNode.setPosition('absolute');
this._domNode.setLayerHinting(true);
this._domNode.setContain('strict');
this._zoneManager = new OverviewZoneManager((lineNumber) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber));
this._zoneManager.setDOMWidth(0);
this._zoneManager.setDOMHeight(0);
this._zoneManager.setOuterHeight(this._context.viewLayout.getScrollHeight());
this._zoneManager.setLineHeight(options.get(67 /* EditorOption.lineHeight */));
this._zoneManager.setPixelRatio(options.get(144 /* EditorOption.pixelRatio */));
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
super.dispose();
}
// ---- begin view event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
if (e.hasChanged(67 /* EditorOption.lineHeight */)) {
this._zoneManager.setLineHeight(options.get(67 /* EditorOption.lineHeight */));
this._render();
}
if (e.hasChanged(144 /* EditorOption.pixelRatio */)) {
this._zoneManager.setPixelRatio(options.get(144 /* EditorOption.pixelRatio */));
this._domNode.setWidth(this._zoneManager.getDOMWidth());
this._domNode.setHeight(this._zoneManager.getDOMHeight());
this._domNode.domNode.width = this._zoneManager.getCanvasWidth();
this._domNode.domNode.height = this._zoneManager.getCanvasHeight();
this._render();
}
return true;
}
onFlushed(e) {
this._render();
return true;
}
onScrollChanged(e) {
if (e.scrollHeightChanged) {
this._zoneManager.setOuterHeight(e.scrollHeight);
this._render();
}
return true;
}
onZonesChanged(e) {
this._render();
return true;
}
// ---- end view event handlers
getDomNode() {
return this._domNode.domNode;
}
setLayout(position) {
this._domNode.setTop(position.top);
this._domNode.setRight(position.right);
let hasChanged = false;
hasChanged = this._zoneManager.setDOMWidth(position.width) || hasChanged;
hasChanged = this._zoneManager.setDOMHeight(position.height) || hasChanged;
if (hasChanged) {
this._domNode.setWidth(this._zoneManager.getDOMWidth());
this._domNode.setHeight(this._zoneManager.getDOMHeight());
this._domNode.domNode.width = this._zoneManager.getCanvasWidth();
this._domNode.domNode.height = this._zoneManager.getCanvasHeight();
this._render();
}
}
setZones(zones) {
this._zoneManager.setZones(zones);
this._render();
}
_render() {
if (this._zoneManager.getOuterHeight() === 0) {
return false;
}
const width = this._zoneManager.getCanvasWidth();
const height = this._zoneManager.getCanvasHeight();
const colorZones = this._zoneManager.resolveColorZones();
const id2Color = this._zoneManager.getId2Color();
const ctx = this._domNode.domNode.getContext('2d');
ctx.clearRect(0, 0, width, height);
if (colorZones.length > 0) {
this._renderOneLane(ctx, colorZones, id2Color, width);
}
return true;
}
_renderOneLane(ctx, colorZones, id2Color, width) {
let currentColorId = 0;
let currentFrom = 0;
let currentTo = 0;
for (const zone of colorZones) {
const zoneColorId = zone.colorId;
const zoneFrom = zone.from;
const zoneTo = zone.to;
if (zoneColorId !== currentColorId) {
ctx.fillRect(0, currentFrom, width, currentTo - currentFrom);
currentColorId = zoneColorId;
ctx.fillStyle = id2Color[currentColorId];
currentFrom = zoneFrom;
currentTo = zoneTo;
}
else {
if (currentTo >= zoneFrom) {
currentTo = Math.max(currentTo, zoneTo);
}
else {
ctx.fillRect(0, currentFrom, width, currentTo - currentFrom);
currentFrom = zoneFrom;
currentTo = zoneTo;
}
}
}
ctx.fillRect(0, currentFrom, width, currentTo - currentFrom);
}
}
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .view-ruler {
position: absolute;
top: 0;
box-shadow: 1px 0 0 0 var(--vscode-editorRuler-foreground) inset;
}
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './rulers.css';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { ViewPart } from '../../view/viewPart.js';
export class Rulers extends ViewPart {
constructor(context) {
super(context);
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.domNode.setClassName('view-rulers');
this._renderedRulers = [];
const options = this._context.configuration.options;
this._rulers = options.get(103 /* EditorOption.rulers */);
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
}
dispose() {
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
this._rulers = options.get(103 /* EditorOption.rulers */);
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
return true;
}
onScrollChanged(e) {
return e.scrollHeightChanged;
}
// --- end event handlers
prepareRender(ctx) {
// Nothing to read
}
_ensureRulersCount() {
const currentCount = this._renderedRulers.length;
const desiredCount = this._rulers.length;
if (currentCount === desiredCount) {
// Nothing to do
return;
}
if (currentCount < desiredCount) {
const { tabSize } = this._context.viewModel.model.getOptions();
const rulerWidth = tabSize;
let addCount = desiredCount - currentCount;
while (addCount > 0) {
const node = createFastDomNode(document.createElement('div'));
node.setClassName('view-ruler');
node.setWidth(rulerWidth);
this.domNode.appendChild(node);
this._renderedRulers.push(node);
addCount--;
}
return;
}
let removeCount = currentCount - desiredCount;
while (removeCount > 0) {
const node = this._renderedRulers.pop();
this.domNode.removeChild(node);
removeCount--;
}
}
render(ctx) {
this._ensureRulersCount();
for (let i = 0, len = this._rulers.length; i < len; i++) {
const node = this._renderedRulers[i];
const ruler = this._rulers[i];
node.setBoxShadow(ruler.color ? `1px 0 0 0 ${ruler.color} inset` : ``);
node.setHeight(Math.min(ctx.scrollHeight, 1000000));
node.setLeft(ruler.column * this._typicalHalfwidthCharacterWidth);
}
}
}
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .scroll-decoration {
position: absolute;
top: 0;
left: 0;
height: 6px;
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset;
}
@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './scrollDecoration.css';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { ViewPart } from '../../view/viewPart.js';
export class ScrollDecorationViewPart extends ViewPart {
constructor(context) {
super(context);
this._scrollTop = 0;
this._width = 0;
this._updateWidth();
this._shouldShow = false;
const options = this._context.configuration.options;
const scrollbar = options.get(104 /* EditorOption.scrollbar */);
this._useShadows = scrollbar.useShadows;
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
}
dispose() {
super.dispose();
}
_updateShouldShow() {
const newShouldShow = (this._useShadows && this._scrollTop > 0);
if (this._shouldShow !== newShouldShow) {
this._shouldShow = newShouldShow;
return true;
}
return false;
}
getDomNode() {
return this._domNode;
}
_updateWidth() {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
if (layoutInfo.minimap.renderMinimap === 0 || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) {
this._width = layoutInfo.width;
}
else {
this._width = layoutInfo.width - layoutInfo.verticalScrollbarWidth;
}
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const scrollbar = options.get(104 /* EditorOption.scrollbar */);
this._useShadows = scrollbar.useShadows;
this._updateWidth();
this._updateShouldShow();
return true;
}
onScrollChanged(e) {
this._scrollTop = e.scrollTop;
return this._updateShouldShow();
}
// --- end event handlers
prepareRender(ctx) {
// Nothing to read
}
render(ctx) {
this._domNode.setWidth(this._width);
this._domNode.setClassName(this._shouldShow ? 'scroll-decoration' : '');
}
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
Keeping name short for faster parsing.
cslr = core selections layer rendering (div)
*/
.monaco-editor .lines-content .cslr {
position: absolute;
}
.monaco-editor .focused .selected-text {
background-color: var(--vscode-editor-selectionBackground);
}
.monaco-editor .selected-text {
background-color: var(--vscode-editor-inactiveSelectionBackground);
}
.monaco-editor .top-left-radius { border-top-left-radius: 3px; }
.monaco-editor .bottom-left-radius { border-bottom-left-radius: 3px; }
.monaco-editor .top-right-radius { border-top-right-radius: 3px; }
.monaco-editor .bottom-right-radius { border-bottom-right-radius: 3px; }
.monaco-editor.hc-black .top-left-radius { border-top-left-radius: 0; }
.monaco-editor.hc-black .bottom-left-radius { border-bottom-left-radius: 0; }
.monaco-editor.hc-black .top-right-radius { border-top-right-radius: 0; }
.monaco-editor.hc-black .bottom-right-radius { border-bottom-right-radius: 0; }
.monaco-editor.hc-light .top-left-radius { border-top-left-radius: 0; }
.monaco-editor.hc-light .bottom-left-radius { border-bottom-left-radius: 0; }
.monaco-editor.hc-light .top-right-radius { border-top-right-radius: 0; }
.monaco-editor.hc-light .bottom-right-radius { border-bottom-right-radius: 0; }
@@ -0,0 +1,322 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './selections.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { editorSelectionForeground } from '../../../../platform/theme/common/colorRegistry.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
class HorizontalRangeWithStyle {
constructor(other) {
this.left = other.left;
this.width = other.width;
this.startStyle = null;
this.endStyle = null;
}
}
class LineVisibleRangesWithStyle {
constructor(lineNumber, ranges) {
this.lineNumber = lineNumber;
this.ranges = ranges;
}
}
function toStyledRange(item) {
return new HorizontalRangeWithStyle(item);
}
function toStyled(item) {
return new LineVisibleRangesWithStyle(item.lineNumber, item.ranges.map(toStyledRange));
}
export class SelectionsOverlay extends DynamicViewOverlay {
static { this.SELECTION_CLASS_NAME = 'selected-text'; }
static { this.SELECTION_TOP_LEFT = 'top-left-radius'; }
static { this.SELECTION_BOTTOM_LEFT = 'bottom-left-radius'; }
static { this.SELECTION_TOP_RIGHT = 'top-right-radius'; }
static { this.SELECTION_BOTTOM_RIGHT = 'bottom-right-radius'; }
static { this.EDITOR_BACKGROUND_CLASS_NAME = 'monaco-editor-background'; }
static { this.ROUNDED_PIECE_WIDTH = 10; }
constructor(context) {
super();
this._previousFrameVisibleRangesWithStyle = [];
this._context = context;
const options = this._context.configuration.options;
this._roundedSelection = options.get(102 /* EditorOption.roundedSelection */);
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
this._selections = [];
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
this._roundedSelection = options.get(102 /* EditorOption.roundedSelection */);
this._typicalHalfwidthCharacterWidth = options.get(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
return true;
}
onCursorStateChanged(e) {
this._selections = e.selections.slice(0);
return true;
}
onDecorationsChanged(e) {
// true for inline decorations that can end up relayouting text
return true; //e.inlineDecorationsChanged;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
_visibleRangesHaveGaps(linesVisibleRanges) {
for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
const lineVisibleRanges = linesVisibleRanges[i];
if (lineVisibleRanges.ranges.length > 1) {
// There are two ranges on the same line
return true;
}
}
return false;
}
_enrichVisibleRangesWithStyle(viewport, linesVisibleRanges, previousFrame) {
const epsilon = this._typicalHalfwidthCharacterWidth / 4;
let previousFrameTop = null;
let previousFrameBottom = null;
if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) {
const topLineNumber = linesVisibleRanges[0].lineNumber;
if (topLineNumber === viewport.startLineNumber) {
for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) {
if (previousFrame[i].lineNumber === topLineNumber) {
previousFrameTop = previousFrame[i].ranges[0];
}
}
}
const bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber;
if (bottomLineNumber === viewport.endLineNumber) {
for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) {
if (previousFrame[i].lineNumber === bottomLineNumber) {
previousFrameBottom = previousFrame[i].ranges[0];
}
}
}
if (previousFrameTop && !previousFrameTop.startStyle) {
previousFrameTop = null;
}
if (previousFrameBottom && !previousFrameBottom.startStyle) {
previousFrameBottom = null;
}
}
for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
// We know for a fact that there is precisely one range on each line
const curLineRange = linesVisibleRanges[i].ranges[0];
const curLeft = curLineRange.left;
const curRight = curLineRange.left + curLineRange.width;
const startStyle = {
top: 0 /* CornerStyle.EXTERN */,
bottom: 0 /* CornerStyle.EXTERN */
};
const endStyle = {
top: 0 /* CornerStyle.EXTERN */,
bottom: 0 /* CornerStyle.EXTERN */
};
if (i > 0) {
// Look above
const prevLeft = linesVisibleRanges[i - 1].ranges[0].left;
const prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width;
if (abs(curLeft - prevLeft) < epsilon) {
startStyle.top = 2 /* CornerStyle.FLAT */;
}
else if (curLeft > prevLeft) {
startStyle.top = 1 /* CornerStyle.INTERN */;
}
if (abs(curRight - prevRight) < epsilon) {
endStyle.top = 2 /* CornerStyle.FLAT */;
}
else if (prevLeft < curRight && curRight < prevRight) {
endStyle.top = 1 /* CornerStyle.INTERN */;
}
}
else if (previousFrameTop) {
// Accept some hiccups near the viewport edges to save on repaints
startStyle.top = previousFrameTop.startStyle.top;
endStyle.top = previousFrameTop.endStyle.top;
}
if (i + 1 < len) {
// Look below
const nextLeft = linesVisibleRanges[i + 1].ranges[0].left;
const nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width;
if (abs(curLeft - nextLeft) < epsilon) {
startStyle.bottom = 2 /* CornerStyle.FLAT */;
}
else if (nextLeft < curLeft && curLeft < nextRight) {
startStyle.bottom = 1 /* CornerStyle.INTERN */;
}
if (abs(curRight - nextRight) < epsilon) {
endStyle.bottom = 2 /* CornerStyle.FLAT */;
}
else if (curRight < nextRight) {
endStyle.bottom = 1 /* CornerStyle.INTERN */;
}
}
else if (previousFrameBottom) {
// Accept some hiccups near the viewport edges to save on repaints
startStyle.bottom = previousFrameBottom.startStyle.bottom;
endStyle.bottom = previousFrameBottom.endStyle.bottom;
}
curLineRange.startStyle = startStyle;
curLineRange.endStyle = endStyle;
}
}
_getVisibleRangesWithStyle(selection, ctx, previousFrame) {
const _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || [];
const linesVisibleRanges = _linesVisibleRanges.map(toStyled);
const visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
if (!visibleRangesHaveGaps && this._roundedSelection) {
this._enrichVisibleRangesWithStyle(ctx.visibleRange, linesVisibleRanges, previousFrame);
}
// The visible ranges are sorted TOP-BOTTOM and LEFT-RIGHT
return linesVisibleRanges;
}
_createSelectionPiece(top, bottom, className, left, width) {
return ('<div class="cslr '
+ className
+ '" style="'
+ 'top:' + top.toString() + 'px;'
+ 'bottom:' + bottom.toString() + 'px;'
+ 'left:' + left.toString() + 'px;'
+ 'width:' + width.toString() + 'px;'
+ '"></div>');
}
_actualRenderOneSelection(output2, visibleStartLineNumber, hasMultipleSelections, visibleRanges) {
if (visibleRanges.length === 0) {
return;
}
const visibleRangesHaveStyle = !!visibleRanges[0].ranges[0].startStyle;
const firstLineNumber = visibleRanges[0].lineNumber;
const lastLineNumber = visibleRanges[visibleRanges.length - 1].lineNumber;
for (let i = 0, len = visibleRanges.length; i < len; i++) {
const lineVisibleRanges = visibleRanges[i];
const lineNumber = lineVisibleRanges.lineNumber;
const lineIndex = lineNumber - visibleStartLineNumber;
const top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0;
const bottom = hasMultipleSelections ? (lineNumber !== firstLineNumber && lineNumber === lastLineNumber ? 1 : 0) : 0;
let innerCornerOutput = '';
let restOfSelectionOutput = '';
for (let j = 0, lenJ = lineVisibleRanges.ranges.length; j < lenJ; j++) {
const visibleRange = lineVisibleRanges.ranges[j];
if (visibleRangesHaveStyle) {
const startStyle = visibleRange.startStyle;
const endStyle = visibleRange.endStyle;
if (startStyle.top === 1 /* CornerStyle.INTERN */ || startStyle.bottom === 1 /* CornerStyle.INTERN */) {
// Reverse rounded corner to the left
// First comes the selection (blue layer)
innerCornerOutput += this._createSelectionPiece(top, bottom, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
// Second comes the background (white layer) with inverse border radius
let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
if (startStyle.top === 1 /* CornerStyle.INTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
}
if (startStyle.bottom === 1 /* CornerStyle.INTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
}
innerCornerOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
}
if (endStyle.top === 1 /* CornerStyle.INTERN */ || endStyle.bottom === 1 /* CornerStyle.INTERN */) {
// Reverse rounded corner to the right
// First comes the selection (blue layer)
innerCornerOutput += this._createSelectionPiece(top, bottom, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
// Second comes the background (white layer) with inverse border radius
let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
if (endStyle.top === 1 /* CornerStyle.INTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
}
if (endStyle.bottom === 1 /* CornerStyle.INTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
}
innerCornerOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
}
}
let className = SelectionsOverlay.SELECTION_CLASS_NAME;
if (visibleRangesHaveStyle) {
const startStyle = visibleRange.startStyle;
const endStyle = visibleRange.endStyle;
if (startStyle.top === 0 /* CornerStyle.EXTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
}
if (startStyle.bottom === 0 /* CornerStyle.EXTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
}
if (endStyle.top === 0 /* CornerStyle.EXTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
}
if (endStyle.bottom === 0 /* CornerStyle.EXTERN */) {
className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
}
}
restOfSelectionOutput += this._createSelectionPiece(top, bottom, className, visibleRange.left, visibleRange.width);
}
output2[lineIndex][0] += innerCornerOutput;
output2[lineIndex][1] += restOfSelectionOutput;
}
}
prepareRender(ctx) {
// Build HTML for inner corners separate from HTML for the rest of selections,
// as the inner corner HTML can interfere with that of other selections.
// In final render, make sure to place the inner corner HTML before the rest of selection HTML. See issue #77777.
const output = [];
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = ['', ''];
}
const thisFrameVisibleRangesWithStyle = [];
for (let i = 0, len = this._selections.length; i < len; i++) {
const selection = this._selections[i];
if (selection.isEmpty()) {
thisFrameVisibleRangesWithStyle[i] = null;
continue;
}
const visibleRangesWithStyle = this._getVisibleRangesWithStyle(selection, ctx, this._previousFrameVisibleRangesWithStyle[i]);
thisFrameVisibleRangesWithStyle[i] = visibleRangesWithStyle;
this._actualRenderOneSelection(output, visibleStartLineNumber, this._selections.length > 1, visibleRangesWithStyle);
}
this._previousFrameVisibleRangesWithStyle = thisFrameVisibleRangesWithStyle;
this._renderResult = output.map(([internalCorners, restOfSelection]) => internalCorners + restOfSelection);
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
registerThemingParticipant((theme, collector) => {
const editorSelectionForegroundColor = theme.getColor(editorSelectionForeground);
if (editorSelectionForegroundColor && !editorSelectionForegroundColor.isTransparent()) {
collector.addRule(`.monaco-editor .view-line span.inline-selected-text { color: ${editorSelectionForegroundColor}; }`);
}
});
function abs(n) {
return n < 0 ? -n : n;
}
@@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* 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
};
}
}
@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .cursors-layer {
position: absolute;
top: 0;
}
.monaco-editor .cursors-layer > .cursor {
position: absolute;
overflow: hidden;
box-sizing: border-box;
}
/* -- smooth-caret-animation -- */
.monaco-editor .cursors-layer.cursor-smooth-caret-animation > .cursor {
transition: all 80ms;
}
/* -- block-outline-style -- */
.monaco-editor .cursors-layer.cursor-block-outline-style > .cursor {
background: transparent !important;
border-style: solid;
border-width: 1px;
}
/* -- underline-style -- */
.monaco-editor .cursors-layer.cursor-underline-style > .cursor {
border-bottom-width: 2px;
border-bottom-style: solid;
background: transparent !important;
}
/* -- underline-thin-style -- */
.monaco-editor .cursors-layer.cursor-underline-thin-style > .cursor {
border-bottom-width: 1px;
border-bottom-style: solid;
background: transparent !important;
}
@keyframes monaco-cursor-smooth {
0%,
20% {
opacity: 1;
}
60%,
100% {
opacity: 0;
}
}
@keyframes monaco-cursor-phase {
0%,
20% {
opacity: 1;
}
90%,
100% {
opacity: 0;
}
}
@keyframes monaco-cursor-expand {
0%,
20% {
transform: scaleY(1);
}
80%,
100% {
transform: scaleY(0);
}
}
.cursor-smooth {
animation: monaco-cursor-smooth 0.5s ease-in-out 0s 20 alternate;
}
.cursor-phase {
animation: monaco-cursor-phase 0.5s ease-in-out 0s 20 alternate;
}
.cursor-expand > .cursor {
animation: monaco-cursor-expand 0.5s ease-in-out 0s 20 alternate;
}
@@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './viewCursors.css';
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { TimeoutTimer } from '../../../../base/common/async.js';
import { ViewPart } from '../../view/viewPart.js';
import { ViewCursor, CursorPlurality } from './viewCursor.js';
import { TextEditorCursorStyle } from '../../../common/config/editorOptions.js';
import { editorCursorBackground, editorCursorForeground, editorMultiCursorPrimaryForeground, editorMultiCursorPrimaryBackground, editorMultiCursorSecondaryForeground, editorMultiCursorSecondaryBackground } from '../../../common/core/editorColorRegistry.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { isHighContrast } from '../../../../platform/theme/common/theme.js';
import { WindowIntervalTimer, getWindow } from '../../../../base/browser/dom.js';
export class ViewCursors extends ViewPart {
static { this.BLINK_INTERVAL = 500; }
constructor(context) {
super(context);
const options = this._context.configuration.options;
this._readOnly = options.get(92 /* EditorOption.readOnly */);
this._cursorBlinking = options.get(26 /* EditorOption.cursorBlinking */);
this._cursorStyle = options.get(28 /* EditorOption.cursorStyle */);
this._cursorSmoothCaretAnimation = options.get(27 /* EditorOption.cursorSmoothCaretAnimation */);
this._selectionIsEmpty = true;
this._isComposingInput = false;
this._isVisible = false;
this._primaryCursor = new ViewCursor(this._context, CursorPlurality.Single);
this._secondaryCursors = [];
this._renderData = [];
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
this._updateDomClassName();
this._domNode.appendChild(this._primaryCursor.getDomNode());
this._startCursorBlinkAnimation = new TimeoutTimer();
this._cursorFlatBlinkInterval = new WindowIntervalTimer();
this._blinkingEnabled = false;
this._editorHasFocus = false;
this._updateBlinking();
}
dispose() {
super.dispose();
this._startCursorBlinkAnimation.dispose();
this._cursorFlatBlinkInterval.dispose();
}
getDomNode() {
return this._domNode;
}
// --- begin event handlers
onCompositionStart(e) {
this._isComposingInput = true;
this._updateBlinking();
return true;
}
onCompositionEnd(e) {
this._isComposingInput = false;
this._updateBlinking();
return true;
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
this._readOnly = options.get(92 /* EditorOption.readOnly */);
this._cursorBlinking = options.get(26 /* EditorOption.cursorBlinking */);
this._cursorStyle = options.get(28 /* EditorOption.cursorStyle */);
this._cursorSmoothCaretAnimation = options.get(27 /* EditorOption.cursorSmoothCaretAnimation */);
this._updateBlinking();
this._updateDomClassName();
this._primaryCursor.onConfigurationChanged(e);
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].onConfigurationChanged(e);
}
return true;
}
_onCursorPositionChanged(position, secondaryPositions, reason) {
const pauseAnimation = (this._secondaryCursors.length !== secondaryPositions.length
|| (this._cursorSmoothCaretAnimation === 'explicit' && reason !== 3 /* CursorChangeReason.Explicit */));
this._primaryCursor.setPlurality(secondaryPositions.length ? CursorPlurality.MultiPrimary : CursorPlurality.Single);
this._primaryCursor.onCursorPositionChanged(position, pauseAnimation);
this._updateBlinking();
if (this._secondaryCursors.length < secondaryPositions.length) {
// Create new cursors
const addCnt = secondaryPositions.length - this._secondaryCursors.length;
for (let i = 0; i < addCnt; i++) {
const newCursor = new ViewCursor(this._context, CursorPlurality.MultiSecondary);
this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
this._secondaryCursors.push(newCursor);
}
}
else if (this._secondaryCursors.length > secondaryPositions.length) {
// Remove some cursors
const removeCnt = this._secondaryCursors.length - secondaryPositions.length;
for (let i = 0; i < removeCnt; i++) {
this._domNode.removeChild(this._secondaryCursors[0].getDomNode());
this._secondaryCursors.splice(0, 1);
}
}
for (let i = 0; i < secondaryPositions.length; i++) {
this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i], pauseAnimation);
}
}
onCursorStateChanged(e) {
const positions = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
positions[i] = e.selections[i].getPosition();
}
this._onCursorPositionChanged(positions[0], positions.slice(1), e.reason);
const selectionIsEmpty = e.selections[0].isEmpty();
if (this._selectionIsEmpty !== selectionIsEmpty) {
this._selectionIsEmpty = selectionIsEmpty;
this._updateDomClassName();
}
return true;
}
onDecorationsChanged(e) {
// true for inline decorations that can end up relayouting text
return true;
}
onFlushed(e) {
return true;
}
onFocusChanged(e) {
this._editorHasFocus = e.isFocused;
this._updateBlinking();
return false;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return true;
}
onTokensChanged(e) {
const shouldRender = (position) => {
for (let i = 0, len = e.ranges.length; i < len; i++) {
if (e.ranges[i].fromLineNumber <= position.lineNumber && position.lineNumber <= e.ranges[i].toLineNumber) {
return true;
}
}
return false;
};
if (shouldRender(this._primaryCursor.getPosition())) {
return true;
}
for (const secondaryCursor of this._secondaryCursors) {
if (shouldRender(secondaryCursor.getPosition())) {
return true;
}
}
return false;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
// ---- blinking logic
_getCursorBlinking() {
if (this._isComposingInput) {
// avoid double cursors
return 0 /* TextEditorCursorBlinkingStyle.Hidden */;
}
if (!this._editorHasFocus) {
return 0 /* TextEditorCursorBlinkingStyle.Hidden */;
}
if (this._readOnly) {
return 5 /* TextEditorCursorBlinkingStyle.Solid */;
}
return this._cursorBlinking;
}
_updateBlinking() {
this._startCursorBlinkAnimation.cancel();
this._cursorFlatBlinkInterval.cancel();
const blinkingStyle = this._getCursorBlinking();
// hidden and solid are special as they involve no animations
const isHidden = (blinkingStyle === 0 /* TextEditorCursorBlinkingStyle.Hidden */);
const isSolid = (blinkingStyle === 5 /* TextEditorCursorBlinkingStyle.Solid */);
if (isHidden) {
this._hide();
}
else {
this._show();
}
this._blinkingEnabled = false;
this._updateDomClassName();
if (!isHidden && !isSolid) {
if (blinkingStyle === 1 /* TextEditorCursorBlinkingStyle.Blink */) {
// flat blinking is handled by JavaScript to save battery life due to Chromium step timing issue https://bugs.chromium.org/p/chromium/issues/detail?id=361587
this._cursorFlatBlinkInterval.cancelAndSet(() => {
if (this._isVisible) {
this._hide();
}
else {
this._show();
}
}, ViewCursors.BLINK_INTERVAL, getWindow(this._domNode.domNode));
}
else {
this._startCursorBlinkAnimation.setIfNotSet(() => {
this._blinkingEnabled = true;
this._updateDomClassName();
}, ViewCursors.BLINK_INTERVAL);
}
}
}
// --- end blinking logic
_updateDomClassName() {
this._domNode.setClassName(this._getClassName());
}
_getClassName() {
let result = 'cursors-layer';
if (!this._selectionIsEmpty) {
result += ' has-selection';
}
switch (this._cursorStyle) {
case TextEditorCursorStyle.Line:
result += ' cursor-line-style';
break;
case TextEditorCursorStyle.Block:
result += ' cursor-block-style';
break;
case TextEditorCursorStyle.Underline:
result += ' cursor-underline-style';
break;
case TextEditorCursorStyle.LineThin:
result += ' cursor-line-thin-style';
break;
case TextEditorCursorStyle.BlockOutline:
result += ' cursor-block-outline-style';
break;
case TextEditorCursorStyle.UnderlineThin:
result += ' cursor-underline-thin-style';
break;
default:
result += ' cursor-line-style';
}
if (this._blinkingEnabled) {
switch (this._getCursorBlinking()) {
case 1 /* TextEditorCursorBlinkingStyle.Blink */:
result += ' cursor-blink';
break;
case 2 /* TextEditorCursorBlinkingStyle.Smooth */:
result += ' cursor-smooth';
break;
case 3 /* TextEditorCursorBlinkingStyle.Phase */:
result += ' cursor-phase';
break;
case 4 /* TextEditorCursorBlinkingStyle.Expand */:
result += ' cursor-expand';
break;
case 5 /* TextEditorCursorBlinkingStyle.Solid */:
result += ' cursor-solid';
break;
default:
result += ' cursor-solid';
}
}
else {
result += ' cursor-solid';
}
if (this._cursorSmoothCaretAnimation === 'on' || this._cursorSmoothCaretAnimation === 'explicit') {
result += ' cursor-smooth-caret-animation';
}
return result;
}
_show() {
this._primaryCursor.show();
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].show();
}
this._isVisible = true;
}
_hide() {
this._primaryCursor.hide();
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].hide();
}
this._isVisible = false;
}
// ---- IViewPart implementation
prepareRender(ctx) {
this._primaryCursor.prepareRender(ctx);
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
this._secondaryCursors[i].prepareRender(ctx);
}
}
render(ctx) {
const renderData = [];
let renderDataLen = 0;
const primaryRenderData = this._primaryCursor.render(ctx);
if (primaryRenderData) {
renderData[renderDataLen++] = primaryRenderData;
}
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
const secondaryRenderData = this._secondaryCursors[i].render(ctx);
if (secondaryRenderData) {
renderData[renderDataLen++] = secondaryRenderData;
}
}
this._renderData = renderData;
}
getLastRenderData() {
return this._renderData;
}
}
registerThemingParticipant((theme, collector) => {
const cursorThemes = [
{ class: '.cursor', foreground: editorCursorForeground, background: editorCursorBackground },
{ class: '.cursor-primary', foreground: editorMultiCursorPrimaryForeground, background: editorMultiCursorPrimaryBackground },
{ class: '.cursor-secondary', foreground: editorMultiCursorSecondaryForeground, background: editorMultiCursorSecondaryBackground },
];
for (const cursorTheme of cursorThemes) {
const caret = theme.getColor(cursorTheme.foreground);
if (caret) {
let caretBackground = theme.getColor(cursorTheme.background);
if (!caretBackground) {
caretBackground = caret.opposite();
}
collector.addRule(`.monaco-editor .cursors-layer ${cursorTheme.class} { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`);
if (isHighContrast(theme.type)) {
collector.addRule(`.monaco-editor .cursors-layer.has-selection ${cursorTheme.class} { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`);
}
}
}
});
@@ -0,0 +1,326 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { ViewPart } from '../../view/viewPart.js';
import { Position } from '../../../common/core/position.js';
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
export class ViewZones extends ViewPart {
constructor(context) {
super(context);
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._contentWidth = layoutInfo.contentWidth;
this._contentLeft = layoutInfo.contentLeft;
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName('view-zones');
this.domNode.setPosition('absolute');
this.domNode.setAttribute('role', 'presentation');
this.domNode.setAttribute('aria-hidden', 'true');
this.marginDomNode = createFastDomNode(document.createElement('div'));
this.marginDomNode.setClassName('margin-view-zones');
this.marginDomNode.setPosition('absolute');
this.marginDomNode.setAttribute('role', 'presentation');
this.marginDomNode.setAttribute('aria-hidden', 'true');
this._zones = {};
}
dispose() {
super.dispose();
this._zones = {};
}
// ---- begin view event handlers
_recomputeWhitespacesProps() {
const whitespaces = this._context.viewLayout.getWhitespaces();
const oldWhitespaces = new Map();
for (const whitespace of whitespaces) {
oldWhitespaces.set(whitespace.id, whitespace);
}
let hadAChange = false;
this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
zone.isInHiddenArea = props.isInHiddenArea;
const oldWhitespace = oldWhitespaces.get(id);
if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) {
whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx);
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
}
});
return hadAChange;
}
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const layoutInfo = options.get(146 /* EditorOption.layoutInfo */);
this._lineHeight = options.get(67 /* EditorOption.lineHeight */);
this._contentWidth = layoutInfo.contentWidth;
this._contentLeft = layoutInfo.contentLeft;
if (e.hasChanged(67 /* EditorOption.lineHeight */)) {
this._recomputeWhitespacesProps();
}
return true;
}
onLineMappingChanged(e) {
return this._recomputeWhitespacesProps();
}
onLinesDeleted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged || e.scrollWidthChanged;
}
onZonesChanged(e) {
return true;
}
onLinesInserted(e) {
return true;
}
// ---- end view event handlers
_getZoneOrdinal(zone) {
return zone.ordinal ?? zone.afterColumn ?? 10000;
}
_computeWhitespaceProps(zone) {
if (zone.afterLineNumber === 0) {
return {
isInHiddenArea: false,
afterViewLineNumber: 0,
heightInPx: this._heightInPixels(zone),
minWidthInPx: this._minWidthInPixels(zone)
};
}
let zoneAfterModelPosition;
if (typeof zone.afterColumn !== 'undefined') {
zoneAfterModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zone.afterLineNumber,
column: zone.afterColumn
});
}
else {
const validAfterLineNumber = this._context.viewModel.model.validatePosition({
lineNumber: zone.afterLineNumber,
column: 1
}).lineNumber;
zoneAfterModelPosition = new Position(validAfterLineNumber, this._context.viewModel.model.getLineMaxColumn(validAfterLineNumber));
}
let zoneBeforeModelPosition;
if (zoneAfterModelPosition.column === this._context.viewModel.model.getLineMaxColumn(zoneAfterModelPosition.lineNumber)) {
zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zoneAfterModelPosition.lineNumber + 1,
column: 1
});
}
else {
zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
lineNumber: zoneAfterModelPosition.lineNumber,
column: zoneAfterModelPosition.column + 1
});
}
const viewPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition, zone.afterColumnAffinity, true);
const isVisible = zone.showInHiddenAreas || this._context.viewModel.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition);
return {
isInHiddenArea: !isVisible,
afterViewLineNumber: viewPosition.lineNumber,
heightInPx: (isVisible ? this._heightInPixels(zone) : 0),
minWidthInPx: this._minWidthInPixels(zone)
};
}
changeViewZones(callback) {
let zonesHaveChanged = false;
this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
const changeAccessor = {
addZone: (zone) => {
zonesHaveChanged = true;
return this._addZone(whitespaceAccessor, zone);
},
removeZone: (id) => {
if (!id) {
return;
}
zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged;
},
layoutZone: (id) => {
if (!id) {
return;
}
zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged;
}
};
safeInvoke1Arg(callback, changeAccessor);
// Invalidate changeAccessor
changeAccessor.addZone = invalidFunc;
changeAccessor.removeZone = invalidFunc;
changeAccessor.layoutZone = invalidFunc;
});
return zonesHaveChanged;
}
_addZone(whitespaceAccessor, zone) {
const props = this._computeWhitespaceProps(zone);
const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
const myZone = {
whitespaceId: whitespaceId,
delegate: zone,
isInHiddenArea: props.isInHiddenArea,
isVisible: false,
domNode: createFastDomNode(zone.domNode),
marginDomNode: zone.marginDomNode ? createFastDomNode(zone.marginDomNode) : null
};
this._safeCallOnComputedHeight(myZone.delegate, props.heightInPx);
myZone.domNode.setPosition('absolute');
myZone.domNode.domNode.style.width = '100%';
myZone.domNode.setDisplay('none');
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.domNode.appendChild(myZone.domNode);
if (myZone.marginDomNode) {
myZone.marginDomNode.setPosition('absolute');
myZone.marginDomNode.domNode.style.width = '100%';
myZone.marginDomNode.setDisplay('none');
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.marginDomNode.appendChild(myZone.marginDomNode);
}
this._zones[myZone.whitespaceId] = myZone;
this.setShouldRender();
return myZone.whitespaceId;
}
_removeZone(whitespaceAccessor, id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
delete this._zones[id];
whitespaceAccessor.removeWhitespace(zone.whitespaceId);
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.domNode.removeAttribute('monaco-view-zone');
zone.domNode.domNode.remove();
if (zone.marginDomNode) {
zone.marginDomNode.removeAttribute('monaco-visible-view-zone');
zone.marginDomNode.removeAttribute('monaco-view-zone');
zone.marginDomNode.domNode.remove();
}
this.setShouldRender();
return true;
}
return false;
}
_layoutZone(whitespaceAccessor, id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
zone.isInHiddenArea = props.isInHiddenArea;
// const newOrdinal = this._getZoneOrdinal(zone.delegate);
whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx);
// TODO@Alex: change `newOrdinal` too
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
this.setShouldRender();
return true;
}
return false;
}
shouldSuppressMouseDownOnViewZone(id) {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
return Boolean(zone.delegate.suppressMouseDown);
}
return false;
}
_heightInPixels(zone) {
if (typeof zone.heightInPx === 'number') {
return zone.heightInPx;
}
if (typeof zone.heightInLines === 'number') {
return this._lineHeight * zone.heightInLines;
}
return this._lineHeight;
}
_minWidthInPixels(zone) {
if (typeof zone.minWidthInPx === 'number') {
return zone.minWidthInPx;
}
return 0;
}
_safeCallOnComputedHeight(zone, height) {
if (typeof zone.onComputedHeight === 'function') {
try {
zone.onComputedHeight(height);
}
catch (e) {
onUnexpectedError(e);
}
}
}
_safeCallOnDomNodeTop(zone, top) {
if (typeof zone.onDomNodeTop === 'function') {
try {
zone.onDomNodeTop(top);
}
catch (e) {
onUnexpectedError(e);
}
}
}
prepareRender(ctx) {
// Nothing to read
}
render(ctx) {
const visibleWhitespaces = ctx.viewportData.whitespaceViewportData;
const visibleZones = {};
let hasVisibleZone = false;
for (const visibleWhitespace of visibleWhitespaces) {
if (this._zones[visibleWhitespace.id].isInHiddenArea) {
continue;
}
visibleZones[visibleWhitespace.id] = visibleWhitespace;
hasVisibleZone = true;
}
const keys = Object.keys(this._zones);
for (let i = 0, len = keys.length; i < len; i++) {
const id = keys[i];
const zone = this._zones[id];
let newTop = 0;
let newHeight = 0;
let newDisplay = 'none';
if (visibleZones.hasOwnProperty(id)) {
newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta;
newHeight = visibleZones[id].height;
newDisplay = 'block';
// zone is visible
if (!zone.isVisible) {
zone.domNode.setAttribute('monaco-visible-view-zone', 'true');
zone.isVisible = true;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(visibleZones[id].verticalOffset));
}
else {
if (zone.isVisible) {
zone.domNode.removeAttribute('monaco-visible-view-zone');
zone.isVisible = false;
}
this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(-1000000));
}
zone.domNode.setTop(newTop);
zone.domNode.setHeight(newHeight);
zone.domNode.setDisplay(newDisplay);
if (zone.marginDomNode) {
zone.marginDomNode.setTop(newTop);
zone.marginDomNode.setHeight(newHeight);
zone.marginDomNode.setDisplay(newDisplay);
}
}
if (hasVisibleZone) {
this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
this.marginDomNode.setWidth(this._contentLeft);
}
}
}
function safeInvoke1Arg(func, arg1) {
try {
return func(arg1);
}
catch (e) {
onUnexpectedError(e);
}
}
@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .mwh {
position: absolute;
color: var(--vscode-editorWhitespace-foreground) !important;
}
@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './whitespace.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import * as strings from '../../../../base/common/strings.js';
import { LineRange } from '../../../common/viewLayout/viewLineRenderer.js';
import { Position } from '../../../common/core/position.js';
import { editorWhitespaces } from '../../../common/core/editorColorRegistry.js';
export class WhitespaceOverlay extends DynamicViewOverlay {
constructor(context) {
super();
this._context = context;
this._options = new WhitespaceOptions(this._context.configuration);
this._selection = [];
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const newOptions = new WhitespaceOptions(this._context.configuration);
if (this._options.equals(newOptions)) {
return e.hasChanged(146 /* EditorOption.layoutInfo */);
}
this._options = newOptions;
return true;
}
onCursorStateChanged(e) {
this._selection = e.selections;
if (this._options.renderWhitespace === 'selection') {
return true;
}
return false;
}
onDecorationsChanged(e) {
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged;
}
onZonesChanged(e) {
return true;
}
// --- end event handlers
prepareRender(ctx) {
if (this._options.renderWhitespace === 'none') {
this._renderResult = null;
return;
}
const startLineNumber = ctx.visibleRange.startLineNumber;
const endLineNumber = ctx.visibleRange.endLineNumber;
const lineCount = endLineNumber - startLineNumber + 1;
const needed = new Array(lineCount);
for (let i = 0; i < lineCount; i++) {
needed[i] = true;
}
const viewportData = this._context.viewModel.getMinimapLinesRenderingData(ctx.viewportData.startLineNumber, ctx.viewportData.endLineNumber, needed);
this._renderResult = [];
for (let lineNumber = ctx.viewportData.startLineNumber; lineNumber <= ctx.viewportData.endLineNumber; lineNumber++) {
const lineIndex = lineNumber - ctx.viewportData.startLineNumber;
const lineData = viewportData.data[lineIndex];
let selectionsOnLine = null;
if (this._options.renderWhitespace === 'selection') {
const selections = this._selection;
for (const selection of selections) {
if (selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber) {
// Selection does not intersect line
continue;
}
const startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn);
const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn);
if (startColumn < endColumn) {
if (!selectionsOnLine) {
selectionsOnLine = [];
}
selectionsOnLine.push(new LineRange(startColumn - 1, endColumn - 1));
}
}
}
this._renderResult[lineIndex] = this._applyRenderWhitespace(ctx, lineNumber, selectionsOnLine, lineData);
}
}
_applyRenderWhitespace(ctx, lineNumber, selections, lineData) {
if (this._options.renderWhitespace === 'selection' && !selections) {
return '';
}
if (this._options.renderWhitespace === 'trailing' && lineData.continuesWithWrappedLine) {
return '';
}
const color = this._context.theme.getColor(editorWhitespaces);
const USE_SVG = this._options.renderWithSVG;
const lineContent = lineData.content;
const len = (this._options.stopRenderingLineAfter === -1 ? lineContent.length : Math.min(this._options.stopRenderingLineAfter, lineContent.length));
const continuesWithWrappedLine = lineData.continuesWithWrappedLine;
const fauxIndentLength = lineData.minColumn - 1;
const onlyBoundary = (this._options.renderWhitespace === 'boundary');
const onlyTrailing = (this._options.renderWhitespace === 'trailing');
const lineHeight = this._options.lineHeight;
const middotWidth = this._options.middotWidth;
const wsmiddotWidth = this._options.wsmiddotWidth;
const spaceWidth = this._options.spaceWidth;
const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
const middotDiff = Math.abs(middotWidth - spaceWidth);
// U+2E31 - WORD SEPARATOR MIDDLE DOT
// U+00B7 - MIDDLE DOT
const renderSpaceCharCode = (wsmiddotDiff < middotDiff ? 0x2E31 : 0xB7);
const canUseHalfwidthRightwardsArrow = this._options.canUseHalfwidthRightwardsArrow;
let result = '';
let lineIsEmptyOrWhitespace = false;
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
let lastNonWhitespaceIndex;
if (firstNonWhitespaceIndex === -1) {
lineIsEmptyOrWhitespace = true;
firstNonWhitespaceIndex = len;
lastNonWhitespaceIndex = len;
}
else {
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
}
let currentSelectionIndex = 0;
let currentSelection = selections && selections[currentSelectionIndex];
let maxLeft = 0;
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
if (currentSelection && charIndex >= currentSelection.endOffset) {
currentSelectionIndex++;
currentSelection = selections && selections[currentSelectionIndex];
}
if (chCode !== 9 /* CharCode.Tab */ && chCode !== 32 /* CharCode.Space */) {
continue;
}
if (onlyTrailing && !lineIsEmptyOrWhitespace && charIndex <= lastNonWhitespaceIndex) {
// If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
continue;
}
if (onlyBoundary && charIndex >= firstNonWhitespaceIndex && charIndex <= lastNonWhitespaceIndex && chCode === 32 /* CharCode.Space */) {
// rendering only boundary whitespace
const prevChCode = (charIndex - 1 >= 0 ? lineContent.charCodeAt(charIndex - 1) : 0 /* CharCode.Null */);
const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : 0 /* CharCode.Null */);
if (prevChCode !== 32 /* CharCode.Space */ && nextChCode !== 32 /* CharCode.Space */) {
continue;
}
}
if (onlyBoundary && continuesWithWrappedLine && charIndex === len - 1) {
const prevCharCode = (charIndex - 1 >= 0 ? lineContent.charCodeAt(charIndex - 1) : 0 /* CharCode.Null */);
const isSingleTrailingSpace = (chCode === 32 /* CharCode.Space */ && (prevCharCode !== 32 /* CharCode.Space */ && prevCharCode !== 9 /* CharCode.Tab */));
if (isSingleTrailingSpace) {
continue;
}
}
if (selections && (!currentSelection || currentSelection.startOffset > charIndex || currentSelection.endOffset <= charIndex)) {
// If rendering whitespace on selection, check that the charIndex falls within a selection
continue;
}
const visibleRange = ctx.visibleRangeForPosition(new Position(lineNumber, charIndex + 1));
if (!visibleRange) {
continue;
}
if (USE_SVG) {
maxLeft = Math.max(maxLeft, visibleRange.left);
if (chCode === 9 /* CharCode.Tab */) {
result += this._renderArrow(lineHeight, spaceWidth, visibleRange.left);
}
else {
result += `<circle cx="${(visibleRange.left + spaceWidth / 2).toFixed(2)}" cy="${(lineHeight / 2).toFixed(2)}" r="${(spaceWidth / 7).toFixed(2)}" />`;
}
}
else {
if (chCode === 9 /* CharCode.Tab */) {
result += `<div class="mwh" style="left:${visibleRange.left}px;height:${lineHeight}px;">${canUseHalfwidthRightwardsArrow ? String.fromCharCode(0xFFEB) : String.fromCharCode(0x2192)}</div>`;
}
else {
result += `<div class="mwh" style="left:${visibleRange.left}px;height:${lineHeight}px;">${String.fromCharCode(renderSpaceCharCode)}</div>`;
}
}
}
if (USE_SVG) {
maxLeft = Math.round(maxLeft + spaceWidth);
return (`<svg style="bottom:0;position:absolute;width:${maxLeft}px;height:${lineHeight}px" viewBox="0 0 ${maxLeft} ${lineHeight}" xmlns="http://www.w3.org/2000/svg" fill="${color}">`
+ result
+ `</svg>`);
}
return result;
}
_renderArrow(lineHeight, spaceWidth, left) {
const strokeWidth = spaceWidth / 7;
const width = spaceWidth;
const dy = lineHeight / 2;
const dx = left;
const p1 = { x: 0, y: strokeWidth / 2 };
const p2 = { x: 100 / 125 * width, y: p1.y };
const p3 = { x: p2.x - 0.2 * p2.x, y: p2.y + 0.2 * p2.x };
const p4 = { x: p3.x + 0.1 * p2.x, y: p3.y + 0.1 * p2.x };
const p5 = { x: p4.x + 0.35 * p2.x, y: p4.y - 0.35 * p2.x };
const p6 = { x: p5.x, y: -p5.y };
const p7 = { x: p4.x, y: -p4.y };
const p8 = { x: p3.x, y: -p3.y };
const p9 = { x: p2.x, y: -p2.y };
const p10 = { x: p1.x, y: -p1.y };
const p = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10];
const parts = p.map((p) => `${(dx + p.x).toFixed(2)} ${(dy + p.y).toFixed(2)}`).join(' L ');
return `<path d="M ${parts}" />`;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
class WhitespaceOptions {
constructor(config) {
const options = config.options;
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const experimentalWhitespaceRendering = options.get(38 /* EditorOption.experimentalWhitespaceRendering */);
if (experimentalWhitespaceRendering === 'off') {
// whitespace is rendered in the view line
this.renderWhitespace = 'none';
this.renderWithSVG = false;
}
else if (experimentalWhitespaceRendering === 'svg') {
this.renderWhitespace = options.get(100 /* EditorOption.renderWhitespace */);
this.renderWithSVG = true;
}
else {
this.renderWhitespace = options.get(100 /* EditorOption.renderWhitespace */);
this.renderWithSVG = false;
}
this.spaceWidth = fontInfo.spaceWidth;
this.middotWidth = fontInfo.middotWidth;
this.wsmiddotWidth = fontInfo.wsmiddotWidth;
this.canUseHalfwidthRightwardsArrow = fontInfo.canUseHalfwidthRightwardsArrow;
this.lineHeight = options.get(67 /* EditorOption.lineHeight */);
this.stopRenderingLineAfter = options.get(118 /* EditorOption.stopRenderingLineAfter */);
}
equals(other) {
return (this.renderWhitespace === other.renderWhitespace
&& this.renderWithSVG === other.renderWithSVG
&& this.spaceWidth === other.spaceWidth
&& this.middotWidth === other.middotWidth
&& this.wsmiddotWidth === other.wsmiddotWidth
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
&& this.lineHeight === other.lineHeight
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter);
}
}
@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getWindow, runWhenWindowIdle } from '../../../../base/browser/dom.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js';
export class CodeEditorContributions extends Disposable {
constructor() {
super();
this._editor = null;
this._instantiationService = null;
/**
* Contains all instantiated contributions.
*/
this._instances = this._register(new DisposableMap());
/**
* Contains contributions which are not yet instantiated.
*/
this._pending = new Map();
/**
* Tracks which instantiation kinds are still left in `_pending`.
*/
this._finishedInstantiation = [];
this._finishedInstantiation[0 /* EditorContributionInstantiation.Eager */] = false;
this._finishedInstantiation[1 /* EditorContributionInstantiation.AfterFirstRender */] = false;
this._finishedInstantiation[2 /* EditorContributionInstantiation.BeforeFirstInteraction */] = false;
this._finishedInstantiation[3 /* EditorContributionInstantiation.Eventually */] = false;
}
initialize(editor, contributions, instantiationService) {
this._editor = editor;
this._instantiationService = instantiationService;
for (const desc of contributions) {
if (this._pending.has(desc.id)) {
onUnexpectedError(new Error(`Cannot have two contributions with the same id ${desc.id}`));
continue;
}
this._pending.set(desc.id, desc);
}
this._instantiateSome(0 /* EditorContributionInstantiation.Eager */);
// AfterFirstRender
// - these extensions will be instantiated at the latest 50ms after the first render.
// - but if there is idle time, we will instantiate them sooner.
this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => {
this._instantiateSome(1 /* EditorContributionInstantiation.AfterFirstRender */);
}));
// BeforeFirstInteraction
// - these extensions will be instantiated at the latest before a mouse or a keyboard event.
// - but if there is idle time, we will instantiate them sooner.
this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => {
this._instantiateSome(2 /* EditorContributionInstantiation.BeforeFirstInteraction */);
}));
// Eventually
// - these extensions will only be instantiated when there is idle time.
// - since there is no guarantee that there will ever be idle time, we set a timeout of 5s here.
this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => {
this._instantiateSome(3 /* EditorContributionInstantiation.Eventually */);
}, 5000));
}
saveViewState() {
const contributionsState = {};
for (const [id, contribution] of this._instances) {
if (typeof contribution.saveViewState === 'function') {
contributionsState[id] = contribution.saveViewState();
}
}
return contributionsState;
}
restoreViewState(contributionsState) {
for (const [id, contribution] of this._instances) {
if (typeof contribution.restoreViewState === 'function') {
contribution.restoreViewState(contributionsState[id]);
}
}
}
get(id) {
this._instantiateById(id);
return this._instances.get(id) || null;
}
onBeforeInteractionEvent() {
// this method is called very often by the editor!
this._instantiateSome(2 /* EditorContributionInstantiation.BeforeFirstInteraction */);
}
onAfterModelAttached() {
return runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => {
this._instantiateSome(1 /* EditorContributionInstantiation.AfterFirstRender */);
}, 50);
}
_instantiateSome(instantiation) {
if (this._finishedInstantiation[instantiation]) {
// already done with this instantiation!
return;
}
this._finishedInstantiation[instantiation] = true;
const contribs = this._findPendingContributionsByInstantiation(instantiation);
for (const contrib of contribs) {
this._instantiateById(contrib.id);
}
}
_findPendingContributionsByInstantiation(instantiation) {
const result = [];
for (const [, desc] of this._pending) {
if (desc.instantiation === instantiation) {
result.push(desc);
}
}
return result;
}
_instantiateById(id) {
const desc = this._pending.get(id);
if (!desc) {
return;
}
this._pending.delete(id);
if (!this._instantiationService || !this._editor) {
throw new Error(`Cannot instantiate contributions before being initialized!`);
}
try {
const instance = this._instantiationService.createInstance(desc.ctor, this._editor);
this._instances.set(desc.id, instance);
if (typeof instance.restoreViewState === 'function' && desc.instantiation !== 0 /* EditorContributionInstantiation.Eager */) {
console.warn(`Editor contribution '${desc.id}' should be eager instantiated because it uses saveViewState / restoreViewState.`);
}
}
catch (err) {
onUnexpectedError(err);
}
}
}
@@ -0,0 +1 @@
export {}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* -------------------- IE10 remove auto clear button -------------------- */
::-ms-clear {
display: none;
}
/* All widgets */
/* I am not a big fan of this rule */
.monaco-editor .editor-widget input {
color: inherit;
}
/* -------------------- Editor -------------------- */
.monaco-editor {
position: relative;
overflow: visible;
-webkit-text-size-adjust: 100%;
color: var(--vscode-editor-foreground);
background-color: var(--vscode-editor-background);
overflow-wrap: initial;
}
.monaco-editor-background {
background-color: var(--vscode-editor-background);
}
.monaco-editor .rangeHighlight {
background-color: var(--vscode-editor-rangeHighlightBackground);
box-sizing: border-box;
border: 1px solid var(--vscode-editor-rangeHighlightBorder);
}
.monaco-editor.hc-black .rangeHighlight, .monaco-editor.hc-light .rangeHighlight {
border-style: dotted;
}
.monaco-editor .symbolHighlight {
background-color: var(--vscode-editor-symbolHighlightBackground);
box-sizing: border-box;
border: 1px solid var(--vscode-editor-symbolHighlightBorder);
}
.monaco-editor.hc-black .symbolHighlight, .monaco-editor.hc-light .symbolHighlight {
border-style: dotted;
}
/* -------------------- Misc -------------------- */
.monaco-editor .overflow-guard {
position: relative;
overflow: hidden;
}
.monaco-editor .view-overlays {
position: absolute;
top: 0;
}
.monaco-editor .view-overlays > div, .monaco-editor .margin-view-overlays > div {
position: absolute;
width: 100%;
}
/*
.monaco-editor .auto-closed-character {
opacity: 0.3;
}
*/
.monaco-editor .squiggly-error {
border-bottom: 4px double var(--vscode-editorError-border);
}
.monaco-editor .squiggly-error::before {
display: block;
content: '';
width: 100%;
height: 100%;
background: var(--vscode-editorError-background);
}
.monaco-editor .squiggly-warning {
border-bottom: 4px double var(--vscode-editorWarning-border);
}
.monaco-editor .squiggly-warning::before {
display: block;
content: '';
width: 100%;
height: 100%;
background: var(--vscode-editorWarning-background);
}
.monaco-editor .squiggly-info {
border-bottom: 4px double var(--vscode-editorInfo-border);
}
.monaco-editor .squiggly-info::before {
display: block;
content: '';
width: 100%;
height: 100%;
background: var(--vscode-editorInfo-background);
}
.monaco-editor .squiggly-hint {
border-bottom: 2px dotted var(--vscode-editorHint-border);
}
.monaco-editor.showUnused .squiggly-unnecessary {
border-bottom: 2px dashed var(--vscode-editorUnnecessaryCode-border);
}
.monaco-editor.showDeprecated .squiggly-inline-deprecated {
text-decoration: line-through;
text-decoration-color: var(--vscode-editor-foreground, inherit);
}
@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as objects from '../../../../base/common/objects.js';
import { ICodeEditorService } from '../../services/codeEditorService.js';
import { CodeEditorWidget } from './codeEditorWidget.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
let EmbeddedCodeEditorWidget = class EmbeddedCodeEditorWidget extends CodeEditorWidget {
constructor(domElement, options, codeEditorWidgetOptions, parentEditor, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService) {
super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);
this._parentEditor = parentEditor;
this._overwriteOptions = options;
// Overwrite parent's options
super.updateOptions(this._overwriteOptions);
this._register(parentEditor.onDidChangeConfiguration((e) => this._onParentConfigurationChanged(e)));
}
getParentEditor() {
return this._parentEditor;
}
_onParentConfigurationChanged(e) {
super.updateOptions(this._parentEditor.getRawOptions());
super.updateOptions(this._overwriteOptions);
}
updateOptions(newOptions) {
objects.mixin(this._overwriteOptions, newOptions, true);
super.updateOptions(this._overwriteOptions);
}
};
EmbeddedCodeEditorWidget = __decorate([
__param(4, IInstantiationService),
__param(5, ICodeEditorService),
__param(6, ICommandService),
__param(7, IContextKeyService),
__param(8, IThemeService),
__param(9, INotificationService),
__param(10, IAccessibilityService),
__param(11, ILanguageConfigurationService),
__param(12, ILanguageFeaturesService)
], EmbeddedCodeEditorWidget);
export { EmbeddedCodeEditorWidget };
@@ -0,0 +1,240 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getActiveElement } from '../../../../base/browser/dom.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { EditorAction2 } from '../../editorExtensions.js';
import { ICodeEditorService } from '../../services/codeEditorService.js';
import { DiffEditorWidget } from './diffEditorWidget.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { localize2 } from '../../../../nls.js';
import { Action2, MenuId } from '../../../../platform/actions/common/actions.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import './registrations.contribution.js';
export class ToggleCollapseUnchangedRegions extends Action2 {
constructor() {
super({
id: 'diffEditor.toggleCollapseUnchangedRegions',
title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'),
icon: Codicon.map,
toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'),
precondition: ContextKeyExpr.has('isInDiffEditor'),
menu: {
when: ContextKeyExpr.has('isInDiffEditor'),
id: MenuId.EditorTitle,
order: 22,
group: 'navigation',
},
});
}
run(accessor, ...args) {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue('diffEditor.hideUnchangedRegions.enabled');
configurationService.updateValue('diffEditor.hideUnchangedRegions.enabled', newValue);
}
}
export class ToggleShowMovedCodeBlocks extends Action2 {
constructor() {
super({
id: 'diffEditor.toggleShowMovedCodeBlocks',
title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'),
precondition: ContextKeyExpr.has('isInDiffEditor'),
});
}
run(accessor, ...args) {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue('diffEditor.experimental.showMoves');
configurationService.updateValue('diffEditor.experimental.showMoves', newValue);
}
}
export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 {
constructor() {
super({
id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited',
title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'),
precondition: ContextKeyExpr.has('isInDiffEditor'),
});
}
run(accessor, ...args) {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited');
configurationService.updateValue('diffEditor.useInlineViewWhenSpaceIsLimited', newValue);
}
}
const diffEditorCategory = localize2('diffEditor', "Diff Editor");
export class SwitchSide extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.switchSide',
title: localize2('switchSide', 'Switch Side'),
icon: Codicon.arrowSwap,
precondition: ContextKeyExpr.has('isInDiffEditor'),
f1: true,
category: diffEditorCategory,
});
}
runEditorCommand(accessor, editor, arg) {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget) {
if (arg && arg.dryRun) {
return { destinationSelection: diffEditor.mapToOtherSide().destinationSelection };
}
else {
diffEditor.switchSide();
}
}
return undefined;
}
}
export class ExitCompareMove extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.exitCompareMove',
title: localize2('exitCompareMove', 'Exit Compare Move'),
icon: Codicon.close,
precondition: EditorContextKeys.comparingMovedCode,
f1: false,
category: diffEditorCategory,
keybinding: {
weight: 10000,
primary: 9 /* KeyCode.Escape */,
}
});
}
runEditorCommand(accessor, editor, ...args) {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget) {
diffEditor.exitCompareMove();
}
}
}
export class CollapseAllUnchangedRegions extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.collapseAllUnchangedRegions',
title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'),
icon: Codicon.fold,
precondition: ContextKeyExpr.has('isInDiffEditor'),
f1: true,
category: diffEditorCategory,
});
}
runEditorCommand(accessor, editor, ...args) {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget) {
diffEditor.collapseAllUnchangedRegions();
}
}
}
export class ShowAllUnchangedRegions extends EditorAction2 {
constructor() {
super({
id: 'diffEditor.showAllUnchangedRegions',
title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'),
icon: Codicon.unfold,
precondition: ContextKeyExpr.has('isInDiffEditor'),
f1: true,
category: diffEditorCategory,
});
}
runEditorCommand(accessor, editor, ...args) {
const diffEditor = findFocusedDiffEditor(accessor);
if (diffEditor instanceof DiffEditorWidget) {
diffEditor.showAllUnchangedRegions();
}
}
}
export class RevertHunkOrSelection extends Action2 {
constructor() {
super({
id: 'diffEditor.revert',
title: localize2('revert', 'Revert'),
f1: false,
category: diffEditorCategory,
});
}
run(accessor, arg) {
const diffEditor = findDiffEditor(accessor, arg.originalUri, arg.modifiedUri);
if (diffEditor instanceof DiffEditorWidget) {
diffEditor.revertRangeMappings(arg.mapping.innerChanges ?? []);
}
return undefined;
}
}
const accessibleDiffViewerCategory = localize2('accessibleDiffViewer', "Accessible Diff Viewer");
export class AccessibleDiffViewerNext extends Action2 {
static { this.id = 'editor.action.accessibleDiffViewer.next'; }
constructor() {
super({
id: AccessibleDiffViewerNext.id,
title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'),
category: accessibleDiffViewerCategory,
precondition: ContextKeyExpr.has('isInDiffEditor'),
keybinding: {
primary: 65 /* KeyCode.F7 */,
weight: 100 /* KeybindingWeight.EditorContrib */
},
f1: true,
});
}
run(accessor) {
const diffEditor = findFocusedDiffEditor(accessor);
diffEditor?.accessibleDiffViewerNext();
}
}
export class AccessibleDiffViewerPrev extends Action2 {
static { this.id = 'editor.action.accessibleDiffViewer.prev'; }
constructor() {
super({
id: AccessibleDiffViewerPrev.id,
title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'),
category: accessibleDiffViewerCategory,
precondition: ContextKeyExpr.has('isInDiffEditor'),
keybinding: {
primary: 1024 /* KeyMod.Shift */ | 65 /* KeyCode.F7 */,
weight: 100 /* KeybindingWeight.EditorContrib */
},
f1: true,
});
}
run(accessor) {
const diffEditor = findFocusedDiffEditor(accessor);
diffEditor?.accessibleDiffViewerPrev();
}
}
export function findDiffEditor(accessor, originalUri, modifiedUri) {
const codeEditorService = accessor.get(ICodeEditorService);
const diffEditors = codeEditorService.listDiffEditors();
return diffEditors.find(diffEditor => {
const modified = diffEditor.getModifiedEditor();
const original = diffEditor.getOriginalEditor();
return modified && modified.getModel()?.uri.toString() === modifiedUri.toString()
&& original && original.getModel()?.uri.toString() === originalUri.toString();
}) || null;
}
export function findFocusedDiffEditor(accessor) {
const codeEditorService = accessor.get(ICodeEditorService);
const diffEditors = codeEditorService.listDiffEditors();
const activeElement = getActiveElement();
if (activeElement) {
for (const d of diffEditors) {
const container = d.getContainerDomNode();
if (isElementOrParentOf(container, activeElement)) {
return d;
}
}
}
return null;
}
function isElementOrParentOf(elementOrParent, element) {
let e = element;
while (e) {
if (e === elementOrParent) {
return true;
}
e = e.parentElement;
}
return false;
}
@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-component.diff-review {
user-select: none;
-webkit-user-select: none;
z-index: 99;
}
.monaco-diff-editor .diff-review {
position: absolute;
}
.monaco-component.diff-review .diff-review-line-number {
text-align: right;
display: inline-block;
color: var(--vscode-editorLineNumber-foreground);
}
.monaco-component.diff-review .diff-review-summary {
padding-left: 10px;
}
.monaco-component.diff-review .diff-review-shadow {
position: absolute;
box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset;
}
.monaco-component.diff-review .diff-review-row {
white-space: pre;
}
.monaco-component.diff-review .diff-review-table {
display: table;
min-width: 100%;
}
.monaco-component.diff-review .diff-review-row {
display: table-row;
width: 100%;
}
.monaco-component.diff-review .diff-review-spacer {
display: inline-block;
width: 10px;
vertical-align: middle;
}
.monaco-component.diff-review .diff-review-spacer > .codicon {
font-size: 9px !important;
}
.monaco-component.diff-review .diff-review-actions {
display: inline-block;
position: absolute;
right: 10px;
top: 2px;
z-index: 100;
}
.monaco-component.diff-review .diff-review-actions .action-label {
width: 16px;
height: 16px;
margin: 2px 0;
}
.monaco-component.diff-review .revertButton {
cursor: pointer;
}
@@ -0,0 +1,575 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { addDisposableListener, addStandardDisposableListener, reset } from '../../../../../base/browser/dom.js';
import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTypes.js';
import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js';
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { Action } from '../../../../../base/common/actions.js';
import { forEachAdjacent, groupAdjacentBy } from '../../../../../base/common/arrays.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { Disposable, toDisposable } from '../../../../../base/common/lifecycle.js';
import { autorun, autorunWithStore, derived, derivedWithStore, observableValue, subtransaction, transaction } from '../../../../../base/common/observable.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { applyFontInfo } from '../../../config/domFontInfo.js';
import { applyStyle } from '../utils.js';
import { EditorFontLigatures } from '../../../../common/config/editorOptions.js';
import { LineRange } from '../../../../common/core/lineRange.js';
import { OffsetRange } from '../../../../common/core/offsetRange.js';
import { Position } from '../../../../common/core/position.js';
import { Range } from '../../../../common/core/range.js';
import { LineRangeMapping } from '../../../../common/diff/rangeMapping.js';
import { ILanguageService } from '../../../../common/languages/language.js';
import { LineTokens } from '../../../../common/tokens/lineTokens.js';
import { RenderLineInput, renderViewLine2 } from '../../../../common/viewLayout/viewLineRenderer.js';
import { ViewLineRenderingData } from '../../../../common/viewModel.js';
import { localize } from '../../../../../nls.js';
import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js';
import './accessibleDiffViewer.css';
const accessibleDiffViewerInsertIcon = registerIcon('diff-review-insert', Codicon.add, localize('accessibleDiffViewerInsertIcon', 'Icon for \'Insert\' in accessible diff viewer.'));
const accessibleDiffViewerRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, localize('accessibleDiffViewerRemoveIcon', 'Icon for \'Remove\' in accessible diff viewer.'));
const accessibleDiffViewerCloseIcon = registerIcon('diff-review-close', Codicon.close, localize('accessibleDiffViewerCloseIcon', 'Icon for \'Close\' in accessible diff viewer.'));
let AccessibleDiffViewer = class AccessibleDiffViewer extends Disposable {
static { this._ttPolicy = createTrustedTypesPolicy('diffReview', { createHTML: value => value }); }
constructor(_parentNode, _visible, _setVisible, _canClose, _width, _height, _diffs, _models, _instantiationService) {
super();
this._parentNode = _parentNode;
this._visible = _visible;
this._setVisible = _setVisible;
this._canClose = _canClose;
this._width = _width;
this._height = _height;
this._diffs = _diffs;
this._models = _models;
this._instantiationService = _instantiationService;
this._state = derivedWithStore(this, (reader, store) => {
const visible = this._visible.read(reader);
this._parentNode.style.visibility = visible ? 'visible' : 'hidden';
if (!visible) {
return null;
}
const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._models, this._setVisible, this._canClose));
const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._models));
return { model, view, };
}).recomputeInitiallyAndOnChange(this._store);
}
next() {
transaction(tx => {
const isVisible = this._visible.get();
this._setVisible(true, tx);
if (isVisible) {
this._state.get().model.nextGroup(tx);
}
});
}
prev() {
transaction(tx => {
this._setVisible(true, tx);
this._state.get().model.previousGroup(tx);
});
}
close() {
transaction(tx => {
this._setVisible(false, tx);
});
}
};
AccessibleDiffViewer = __decorate([
__param(8, IInstantiationService)
], AccessibleDiffViewer);
export { AccessibleDiffViewer };
let ViewModel = class ViewModel extends Disposable {
constructor(_diffs, _models, _setVisible, canClose, _accessibilitySignalService) {
super();
this._diffs = _diffs;
this._models = _models;
this._setVisible = _setVisible;
this.canClose = canClose;
this._accessibilitySignalService = _accessibilitySignalService;
this._groups = observableValue(this, []);
this._currentGroupIdx = observableValue(this, 0);
this._currentElementIdx = observableValue(this, 0);
this.groups = this._groups;
this.currentGroup = this._currentGroupIdx.map((idx, r) => this._groups.read(r)[idx]);
this.currentGroupIndex = this._currentGroupIdx;
this.currentElement = this._currentElementIdx.map((idx, r) => this.currentGroup.read(r)?.lines[idx]);
this._register(autorun(reader => {
/** @description update groups */
const diffs = this._diffs.read(reader);
if (!diffs) {
this._groups.set([], undefined);
return;
}
const groups = computeViewElementGroups(diffs, this._models.getOriginalModel().getLineCount(), this._models.getModifiedModel().getLineCount());
transaction(tx => {
const p = this._models.getModifiedPosition();
if (p) {
const nextGroup = groups.findIndex(g => p?.lineNumber < g.range.modified.endLineNumberExclusive);
if (nextGroup !== -1) {
this._currentGroupIdx.set(nextGroup, tx);
}
}
this._groups.set(groups, tx);
});
}));
this._register(autorun(reader => {
/** @description play audio-cue for diff */
const currentViewItem = this.currentElement.read(reader);
if (currentViewItem?.type === LineType.Deleted) {
this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' });
}
else if (currentViewItem?.type === LineType.Added) {
this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' });
}
}));
this._register(autorun(reader => {
/** @description select lines in editor */
// This ensures editor commands (like revert/stage) work
const currentViewItem = this.currentElement.read(reader);
if (currentViewItem && currentViewItem.type !== LineType.Header) {
const lineNumber = currentViewItem.modifiedLineNumber ?? currentViewItem.diff.modified.startLineNumber;
this._models.modifiedSetSelection(Range.fromPositions(new Position(lineNumber, 1)));
}
}));
}
_goToGroupDelta(delta, tx) {
const groups = this.groups.get();
if (!groups || groups.length <= 1) {
return;
}
subtransaction(tx, tx => {
this._currentGroupIdx.set(OffsetRange.ofLength(groups.length).clipCyclic(this._currentGroupIdx.get() + delta), tx);
this._currentElementIdx.set(0, tx);
});
}
nextGroup(tx) { this._goToGroupDelta(1, tx); }
previousGroup(tx) { this._goToGroupDelta(-1, tx); }
_goToLineDelta(delta) {
const group = this.currentGroup.get();
if (!group || group.lines.length <= 1) {
return;
}
transaction(tx => {
this._currentElementIdx.set(OffsetRange.ofLength(group.lines.length).clip(this._currentElementIdx.get() + delta), tx);
});
}
goToNextLine() { this._goToLineDelta(1); }
goToPreviousLine() { this._goToLineDelta(-1); }
goToLine(line) {
const group = this.currentGroup.get();
if (!group) {
return;
}
const idx = group.lines.indexOf(line);
if (idx === -1) {
return;
}
transaction(tx => {
this._currentElementIdx.set(idx, tx);
});
}
revealCurrentElementInEditor() {
if (!this.canClose.get()) {
return;
}
this._setVisible(false, undefined);
const curElem = this.currentElement.get();
if (curElem) {
if (curElem.type === LineType.Deleted) {
this._models.originalReveal(Range.fromPositions(new Position(curElem.originalLineNumber, 1)));
}
else {
this._models.modifiedReveal(curElem.type !== LineType.Header
? Range.fromPositions(new Position(curElem.modifiedLineNumber, 1))
: undefined);
}
}
}
close() {
if (!this.canClose.get()) {
return;
}
this._setVisible(false, undefined);
this._models.modifiedFocus();
}
};
ViewModel = __decorate([
__param(4, IAccessibilitySignalService)
], ViewModel);
const viewElementGroupLineMargin = 3;
function computeViewElementGroups(diffs, originalLineCount, modifiedLineCount) {
const result = [];
for (const g of groupAdjacentBy(diffs, (a, b) => (b.modified.startLineNumber - a.modified.endLineNumberExclusive < 2 * viewElementGroupLineMargin))) {
const viewElements = [];
viewElements.push(new HeaderViewElement());
const origFullRange = new LineRange(Math.max(1, g[0].original.startLineNumber - viewElementGroupLineMargin), Math.min(g[g.length - 1].original.endLineNumberExclusive + viewElementGroupLineMargin, originalLineCount + 1));
const modifiedFullRange = new LineRange(Math.max(1, g[0].modified.startLineNumber - viewElementGroupLineMargin), Math.min(g[g.length - 1].modified.endLineNumberExclusive + viewElementGroupLineMargin, modifiedLineCount + 1));
forEachAdjacent(g, (a, b) => {
const origRange = new LineRange(a ? a.original.endLineNumberExclusive : origFullRange.startLineNumber, b ? b.original.startLineNumber : origFullRange.endLineNumberExclusive);
const modifiedRange = new LineRange(a ? a.modified.endLineNumberExclusive : modifiedFullRange.startLineNumber, b ? b.modified.startLineNumber : modifiedFullRange.endLineNumberExclusive);
origRange.forEach(origLineNumber => {
viewElements.push(new UnchangedLineViewElement(origLineNumber, modifiedRange.startLineNumber + (origLineNumber - origRange.startLineNumber)));
});
if (b) {
b.original.forEach(origLineNumber => {
viewElements.push(new DeletedLineViewElement(b, origLineNumber));
});
b.modified.forEach(modifiedLineNumber => {
viewElements.push(new AddedLineViewElement(b, modifiedLineNumber));
});
}
});
const modifiedRange = g[0].modified.join(g[g.length - 1].modified);
const originalRange = g[0].original.join(g[g.length - 1].original);
result.push(new ViewElementGroup(new LineRangeMapping(modifiedRange, originalRange), viewElements));
}
return result;
}
var LineType;
(function (LineType) {
LineType[LineType["Header"] = 0] = "Header";
LineType[LineType["Unchanged"] = 1] = "Unchanged";
LineType[LineType["Deleted"] = 2] = "Deleted";
LineType[LineType["Added"] = 3] = "Added";
})(LineType || (LineType = {}));
class ViewElementGroup {
constructor(range, lines) {
this.range = range;
this.lines = lines;
}
}
class HeaderViewElement {
constructor() {
this.type = LineType.Header;
}
}
class DeletedLineViewElement {
constructor(diff, originalLineNumber) {
this.diff = diff;
this.originalLineNumber = originalLineNumber;
this.type = LineType.Deleted;
this.modifiedLineNumber = undefined;
}
}
class AddedLineViewElement {
constructor(diff, modifiedLineNumber) {
this.diff = diff;
this.modifiedLineNumber = modifiedLineNumber;
this.type = LineType.Added;
this.originalLineNumber = undefined;
}
}
class UnchangedLineViewElement {
constructor(originalLineNumber, modifiedLineNumber) {
this.originalLineNumber = originalLineNumber;
this.modifiedLineNumber = modifiedLineNumber;
this.type = LineType.Unchanged;
}
}
let View = class View extends Disposable {
constructor(_element, _model, _width, _height, _models, _languageService) {
super();
this._element = _element;
this._model = _model;
this._width = _width;
this._height = _height;
this._models = _models;
this._languageService = _languageService;
this.domNode = this._element;
this.domNode.className = 'monaco-component diff-review monaco-editor-background';
const actionBarContainer = document.createElement('div');
actionBarContainer.className = 'diff-review-actions';
this._actionBar = this._register(new ActionBar(actionBarContainer));
this._register(autorun(reader => {
/** @description update actions */
this._actionBar.clear();
if (this._model.canClose.read(reader)) {
this._actionBar.push(new Action('diffreview.close', localize('label.close', "Close"), 'close-diff-review ' + ThemeIcon.asClassName(accessibleDiffViewerCloseIcon), true, async () => _model.close()), { label: false, icon: true });
}
}));
this._content = document.createElement('div');
this._content.className = 'diff-review-content';
this._content.setAttribute('role', 'code');
this._scrollbar = this._register(new DomScrollableElement(this._content, {}));
reset(this.domNode, this._scrollbar.getDomNode(), actionBarContainer);
this._register(autorun(r => {
this._height.read(r);
this._width.read(r);
this._scrollbar.scanDomNode();
}));
this._register(toDisposable(() => { reset(this.domNode); }));
this._register(applyStyle(this.domNode, { width: this._width, height: this._height }));
this._register(applyStyle(this._content, { width: this._width, height: this._height }));
this._register(autorunWithStore((reader, store) => {
/** @description render */
this._model.currentGroup.read(reader);
this._render(store);
}));
// TODO@hediet use commands
this._register(addStandardDisposableListener(this.domNode, 'keydown', (e) => {
if (e.equals(18 /* KeyCode.DownArrow */)
|| e.equals(2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */)
|| e.equals(512 /* KeyMod.Alt */ | 18 /* KeyCode.DownArrow */)) {
e.preventDefault();
this._model.goToNextLine();
}
if (e.equals(16 /* KeyCode.UpArrow */)
|| e.equals(2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */)
|| e.equals(512 /* KeyMod.Alt */ | 16 /* KeyCode.UpArrow */)) {
e.preventDefault();
this._model.goToPreviousLine();
}
if (e.equals(9 /* KeyCode.Escape */)
|| e.equals(2048 /* KeyMod.CtrlCmd */ | 9 /* KeyCode.Escape */)
|| e.equals(512 /* KeyMod.Alt */ | 9 /* KeyCode.Escape */)
|| e.equals(1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */)) {
e.preventDefault();
this._model.close();
}
if (e.equals(10 /* KeyCode.Space */)
|| e.equals(3 /* KeyCode.Enter */)) {
e.preventDefault();
this._model.revealCurrentElementInEditor();
}
}));
}
_render(store) {
const originalOptions = this._models.getOriginalOptions();
const modifiedOptions = this._models.getModifiedOptions();
const container = document.createElement('div');
container.className = 'diff-review-table';
container.setAttribute('role', 'list');
container.setAttribute('aria-label', localize('ariaLabel', 'Accessible Diff Viewer. Use arrow up and down to navigate.'));
applyFontInfo(container, modifiedOptions.get(50 /* EditorOption.fontInfo */));
reset(this._content, container);
const originalModel = this._models.getOriginalModel();
const modifiedModel = this._models.getModifiedModel();
if (!originalModel || !modifiedModel) {
return;
}
const originalModelOpts = originalModel.getOptions();
const modifiedModelOpts = modifiedModel.getOptions();
const lineHeight = modifiedOptions.get(67 /* EditorOption.lineHeight */);
const group = this._model.currentGroup.get();
for (const viewItem of group?.lines || []) {
if (!group) {
break;
}
let row;
if (viewItem.type === LineType.Header) {
const header = document.createElement('div');
header.className = 'diff-review-row';
header.setAttribute('role', 'listitem');
const r = group.range;
const diffIndex = this._model.currentGroupIndex.get();
const diffsLength = this._model.groups.get().length;
const getAriaLines = (lines) => lines === 0 ? localize('no_lines_changed', "no lines changed")
: lines === 1 ? localize('one_line_changed', "1 line changed")
: localize('more_lines_changed', "{0} lines changed", lines);
const originalChangedLinesCntAria = getAriaLines(r.original.length);
const modifiedChangedLinesCntAria = getAriaLines(r.modified.length);
header.setAttribute('aria-label', localize({
key: 'header',
comment: [
'This is the ARIA label for a git diff header.',
'A git diff header looks like this: @@ -154,12 +159,39 @@.',
'That encodes that at original line 154 (which is now line 159), 12 lines were removed/changed with 39 lines.',
'Variables 0 and 1 refer to the diff index out of total number of diffs.',
'Variables 2 and 4 will be numbers (a line number).',
'Variables 3 and 5 will be "no lines changed", "1 line changed" or "X lines changed", localized separately.'
]
}, "Difference {0} of {1}: original line {2}, {3}, modified line {4}, {5}", (diffIndex + 1), diffsLength, r.original.startLineNumber, originalChangedLinesCntAria, r.modified.startLineNumber, modifiedChangedLinesCntAria));
const cell = document.createElement('div');
cell.className = 'diff-review-cell diff-review-summary';
// e.g.: `1/10: @@ -504,7 +517,7 @@`
cell.appendChild(document.createTextNode(`${diffIndex + 1}/${diffsLength}: @@ -${r.original.startLineNumber},${r.original.length} +${r.modified.startLineNumber},${r.modified.length} @@`));
header.appendChild(cell);
row = header;
}
else {
row = this._createRow(viewItem, lineHeight, this._width.get(), originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts);
}
container.appendChild(row);
const isSelectedObs = derived(reader => /** @description isSelected */ this._model.currentElement.read(reader) === viewItem);
store.add(autorun(reader => {
/** @description update tab index */
const isSelected = isSelectedObs.read(reader);
row.tabIndex = isSelected ? 0 : -1;
if (isSelected) {
row.focus();
}
}));
store.add(addDisposableListener(row, 'focus', () => {
this._model.goToLine(viewItem);
}));
}
this._scrollbar.scanDomNode();
}
_createRow(item, lineHeight, width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts) {
const originalLayoutInfo = originalOptions.get(146 /* EditorOption.layoutInfo */);
const originalLineNumbersWidth = originalLayoutInfo.glyphMarginWidth + originalLayoutInfo.lineNumbersWidth;
const modifiedLayoutInfo = modifiedOptions.get(146 /* EditorOption.layoutInfo */);
const modifiedLineNumbersWidth = 10 + modifiedLayoutInfo.glyphMarginWidth + modifiedLayoutInfo.lineNumbersWidth;
let rowClassName = 'diff-review-row';
let lineNumbersExtraClassName = '';
const spacerClassName = 'diff-review-spacer';
let spacerIcon = null;
switch (item.type) {
case LineType.Added:
rowClassName = 'diff-review-row line-insert';
lineNumbersExtraClassName = ' char-insert';
spacerIcon = accessibleDiffViewerInsertIcon;
break;
case LineType.Deleted:
rowClassName = 'diff-review-row line-delete';
lineNumbersExtraClassName = ' char-delete';
spacerIcon = accessibleDiffViewerRemoveIcon;
break;
}
const row = document.createElement('div');
row.style.minWidth = width + 'px';
row.className = rowClassName;
row.setAttribute('role', 'listitem');
row.ariaLevel = '';
const cell = document.createElement('div');
cell.className = 'diff-review-cell';
cell.style.height = `${lineHeight}px`;
row.appendChild(cell);
const originalLineNumber = document.createElement('span');
originalLineNumber.style.width = (originalLineNumbersWidth + 'px');
originalLineNumber.style.minWidth = (originalLineNumbersWidth + 'px');
originalLineNumber.className = 'diff-review-line-number' + lineNumbersExtraClassName;
if (item.originalLineNumber !== undefined) {
originalLineNumber.appendChild(document.createTextNode(String(item.originalLineNumber)));
}
else {
originalLineNumber.innerText = '\u00a0';
}
cell.appendChild(originalLineNumber);
const modifiedLineNumber = document.createElement('span');
modifiedLineNumber.style.width = (modifiedLineNumbersWidth + 'px');
modifiedLineNumber.style.minWidth = (modifiedLineNumbersWidth + 'px');
modifiedLineNumber.style.paddingRight = '10px';
modifiedLineNumber.className = 'diff-review-line-number' + lineNumbersExtraClassName;
if (item.modifiedLineNumber !== undefined) {
modifiedLineNumber.appendChild(document.createTextNode(String(item.modifiedLineNumber)));
}
else {
modifiedLineNumber.innerText = '\u00a0';
}
cell.appendChild(modifiedLineNumber);
const spacer = document.createElement('span');
spacer.className = spacerClassName;
if (spacerIcon) {
const spacerCodicon = document.createElement('span');
spacerCodicon.className = ThemeIcon.asClassName(spacerIcon);
spacerCodicon.innerText = '\u00a0\u00a0';
spacer.appendChild(spacerCodicon);
}
else {
spacer.innerText = '\u00a0\u00a0';
}
cell.appendChild(spacer);
let lineContent;
if (item.modifiedLineNumber !== undefined) {
let html = this._getLineHtml(modifiedModel, modifiedOptions, modifiedModelOpts.tabSize, item.modifiedLineNumber, this._languageService.languageIdCodec);
if (AccessibleDiffViewer._ttPolicy) {
html = AccessibleDiffViewer._ttPolicy.createHTML(html);
}
cell.insertAdjacentHTML('beforeend', html);
lineContent = modifiedModel.getLineContent(item.modifiedLineNumber);
}
else {
let html = this._getLineHtml(originalModel, originalOptions, originalModelOpts.tabSize, item.originalLineNumber, this._languageService.languageIdCodec);
if (AccessibleDiffViewer._ttPolicy) {
html = AccessibleDiffViewer._ttPolicy.createHTML(html);
}
cell.insertAdjacentHTML('beforeend', html);
lineContent = originalModel.getLineContent(item.originalLineNumber);
}
if (lineContent.length === 0) {
lineContent = localize('blankLine', "blank");
}
let ariaLabel = '';
switch (item.type) {
case LineType.Unchanged:
if (item.originalLineNumber === item.modifiedLineNumber) {
ariaLabel = localize({ key: 'unchangedLine', comment: ['The placeholders are contents of the line and should not be translated.'] }, "{0} unchanged line {1}", lineContent, item.originalLineNumber);
}
else {
ariaLabel = localize('equalLine', "{0} original line {1} modified line {2}", lineContent, item.originalLineNumber, item.modifiedLineNumber);
}
break;
case LineType.Added:
ariaLabel = localize('insertLine', "+ {0} modified line {1}", lineContent, item.modifiedLineNumber);
break;
case LineType.Deleted:
ariaLabel = localize('deleteLine', "- {0} original line {1}", lineContent, item.originalLineNumber);
break;
}
row.setAttribute('aria-label', ariaLabel);
return row;
}
_getLineHtml(model, options, tabSize, lineNumber, languageIdCodec) {
const lineContent = model.getLineContent(lineNumber);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
const lineTokens = LineTokens.createEmpty(lineContent, languageIdCodec);
const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, model.mightContainNonBasicASCII());
const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, model.mightContainRTL());
const r = renderViewLine2(new RenderLineInput((fontInfo.isMonospace && !options.get(33 /* EditorOption.disableMonospaceOptimizations */)), fontInfo.canUseHalfwidthRightwardsArrow, lineContent, false, isBasicASCII, containsRTL, 0, lineTokens, [], tabSize, 0, fontInfo.spaceWidth, fontInfo.middotWidth, fontInfo.wsmiddotWidth, options.get(118 /* EditorOption.stopRenderingLineAfter */), options.get(100 /* EditorOption.renderWhitespace */), options.get(95 /* EditorOption.renderControlCharacters */), options.get(51 /* EditorOption.fontLigatures */) !== EditorFontLigatures.OFF, null));
return r.html;
}
};
View = __decorate([
__param(5, ILanguageService)
], View);
export class AccessibleDiffViewerModelFromEditors {
constructor(editors) {
this.editors = editors;
}
getOriginalModel() {
return this.editors.original.getModel();
}
getOriginalOptions() {
return this.editors.original.getOptions();
}
originalReveal(range) {
this.editors.original.revealRange(range);
this.editors.original.setSelection(range);
this.editors.original.focus();
}
getModifiedModel() {
return this.editors.modified.getModel();
}
getModifiedOptions() {
return this.editors.modified.getOptions();
}
modifiedReveal(range) {
if (range) {
this.editors.modified.revealRange(range);
this.editors.modified.setSelection(range);
}
this.editors.modified.focus();
}
modifiedSetSelection(range) {
this.editors.modified.setSelection(range);
}
modifiedFocus() {
this.editors.modified.focus();
}
getModifiedPosition() {
return this.editors.modified.getPosition() ?? undefined;
}
}
@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { derived } from '../../../../../base/common/observable.js';
import { allowsTrueInlineDiffRendering } from './diffEditorViewZones/diffEditorViewZones.js';
import { MovedBlocksLinesFeature } from '../features/movedBlocksLinesFeature.js';
import { diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from '../registrations.contribution.js';
import { applyObservableDecorations } from '../utils.js';
export class DiffEditorDecorations extends Disposable {
constructor(_editors, _diffModel, _options, widget) {
super();
this._editors = _editors;
this._diffModel = _diffModel;
this._options = _options;
this._decorations = derived(this, (reader) => {
const diffModel = this._diffModel.read(reader);
const diff = diffModel?.diff.read(reader);
if (!diff) {
return null;
}
const movedTextToCompare = this._diffModel.read(reader).movedTextToCompare.read(reader);
const renderIndicators = this._options.renderIndicators.read(reader);
const showEmptyDecorations = this._options.showEmptyDecorations.read(reader);
const originalDecorations = [];
const modifiedDecorations = [];
if (!movedTextToCompare) {
for (const m of diff.mappings) {
if (!m.lineRangeMapping.original.isEmpty) {
originalDecorations.push({ range: m.lineRangeMapping.original.toInclusiveRange(), options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
}
if (!m.lineRangeMapping.modified.isEmpty) {
modifiedDecorations.push({ range: m.lineRangeMapping.modified.toInclusiveRange(), options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
}
if (m.lineRangeMapping.modified.isEmpty || m.lineRangeMapping.original.isEmpty) {
if (!m.lineRangeMapping.original.isEmpty) {
originalDecorations.push({ range: m.lineRangeMapping.original.toInclusiveRange(), options: diffWholeLineDeleteDecoration });
}
if (!m.lineRangeMapping.modified.isEmpty) {
modifiedDecorations.push({ range: m.lineRangeMapping.modified.toInclusiveRange(), options: diffWholeLineAddDecoration });
}
}
else {
const useInlineDiff = this._options.useTrueInlineDiffRendering.read(reader) && allowsTrueInlineDiffRendering(m.lineRangeMapping);
for (const i of m.lineRangeMapping.innerChanges || []) {
// Don't show empty markers outside the line range
if (m.lineRangeMapping.original.contains(i.originalRange.startLineNumber)) {
originalDecorations.push({ range: i.originalRange, options: (i.originalRange.isEmpty() && showEmptyDecorations) ? diffDeleteDecorationEmpty : diffDeleteDecoration });
}
if (m.lineRangeMapping.modified.contains(i.modifiedRange.startLineNumber)) {
modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations && !useInlineDiff) ? diffAddDecorationEmpty : diffAddDecoration });
}
if (useInlineDiff) {
const deletedText = diffModel.model.original.getValueInRange(i.originalRange);
modifiedDecorations.push({
range: i.modifiedRange,
options: {
description: 'deleted-text',
before: {
content: deletedText,
inlineClassName: 'inline-deleted-text',
},
zIndex: 100000,
showIfCollapsed: true,
}
});
}
}
}
}
}
if (movedTextToCompare) {
for (const m of movedTextToCompare.changes) {
const fullRangeOriginal = m.original.toInclusiveRange();
if (fullRangeOriginal) {
originalDecorations.push({ range: fullRangeOriginal, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
}
const fullRangeModified = m.modified.toInclusiveRange();
if (fullRangeModified) {
modifiedDecorations.push({ range: fullRangeModified, options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
}
for (const i of m.innerChanges || []) {
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
}
}
}
const activeMovedText = this._diffModel.read(reader).activeMovedText.read(reader);
for (const m of diff.movedTexts) {
originalDecorations.push({
range: m.lineRangeMapping.original.toInclusiveRange(), options: {
description: 'moved',
blockClassName: 'movedOriginal' + (m === activeMovedText ? ' currentMove' : ''),
blockPadding: [MovedBlocksLinesFeature.movedCodeBlockPadding, 0, MovedBlocksLinesFeature.movedCodeBlockPadding, MovedBlocksLinesFeature.movedCodeBlockPadding],
}
});
modifiedDecorations.push({
range: m.lineRangeMapping.modified.toInclusiveRange(), options: {
description: 'moved',
blockClassName: 'movedModified' + (m === activeMovedText ? ' currentMove' : ''),
blockPadding: [4, 0, 4, 4],
}
});
}
return { originalDecorations, modifiedDecorations };
});
this._register(applyObservableDecorations(this._editors.original, this._decorations.map(d => d?.originalDecorations || [])));
this._register(applyObservableDecorations(this._editors.modified, this._decorations.map(d => d?.modifiedDecorations || [])));
}
}
@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { Emitter } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { autorunHandleChanges, derivedOpts, observableFromEvent } from '../../../../../base/common/observable.js';
import { observableCodeEditor } from '../../../observableCodeEditor.js';
import { OverviewRulerFeature } from '../features/overviewRulerFeature.js';
import { EditorOptions } from '../../../../common/config/editorOptions.js';
import { Position } from '../../../../common/core/position.js';
import { localize } from '../../../../../nls.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
let DiffEditorEditors = class DiffEditorEditors extends Disposable {
get onDidContentSizeChange() { return this._onDidContentSizeChange.event; }
constructor(originalEditorElement, modifiedEditorElement, _options, _argCodeEditorWidgetOptions, _createInnerEditor, _instantiationService, _keybindingService) {
super();
this.originalEditorElement = originalEditorElement;
this.modifiedEditorElement = modifiedEditorElement;
this._options = _options;
this._argCodeEditorWidgetOptions = _argCodeEditorWidgetOptions;
this._createInnerEditor = _createInnerEditor;
this._instantiationService = _instantiationService;
this._keybindingService = _keybindingService;
this.original = this._register(this._createLeftHandSideEditor(this._options.editorOptions.get(), this._argCodeEditorWidgetOptions.originalEditor || {}));
this.modified = this._register(this._createRightHandSideEditor(this._options.editorOptions.get(), this._argCodeEditorWidgetOptions.modifiedEditor || {}));
this._onDidContentSizeChange = this._register(new Emitter());
this.modifiedScrollTop = observableFromEvent(this, this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop());
this.modifiedScrollHeight = observableFromEvent(this, this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight());
this.modifiedObs = observableCodeEditor(this.modified);
this.originalObs = observableCodeEditor(this.original);
this.modifiedModel = this.modifiedObs.model;
this.modifiedSelections = observableFromEvent(this, this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []);
this.modifiedCursor = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1));
this.originalCursor = observableFromEvent(this, this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1));
this._argCodeEditorWidgetOptions = null;
this._register(autorunHandleChanges({
createEmptyChangeSummary: () => ({}),
handleChange: (ctx, changeSummary) => {
if (ctx.didChange(_options.editorOptions)) {
Object.assign(changeSummary, ctx.change.changedOptions);
}
return true;
}
}, (reader, changeSummary) => {
/** @description update editor options */
_options.editorOptions.read(reader);
this._options.renderSideBySide.read(reader);
this.modified.updateOptions(this._adjustOptionsForRightHandSide(reader, changeSummary));
this.original.updateOptions(this._adjustOptionsForLeftHandSide(reader, changeSummary));
}));
}
_createLeftHandSideEditor(options, codeEditorWidgetOptions) {
const leftHandSideOptions = this._adjustOptionsForLeftHandSide(undefined, options);
const editor = this._constructInnerEditor(this._instantiationService, this.originalEditorElement, leftHandSideOptions, codeEditorWidgetOptions);
editor.setContextValue('isInDiffLeftEditor', true);
return editor;
}
_createRightHandSideEditor(options, codeEditorWidgetOptions) {
const rightHandSideOptions = this._adjustOptionsForRightHandSide(undefined, options);
const editor = this._constructInnerEditor(this._instantiationService, this.modifiedEditorElement, rightHandSideOptions, codeEditorWidgetOptions);
editor.setContextValue('isInDiffRightEditor', true);
return editor;
}
_constructInnerEditor(instantiationService, container, options, editorWidgetOptions) {
const editor = this._createInnerEditor(instantiationService, container, options, editorWidgetOptions);
this._register(editor.onDidContentSizeChange(e => {
const width = this.original.getContentWidth() + this.modified.getContentWidth() + OverviewRulerFeature.ENTIRE_DIFF_OVERVIEW_WIDTH;
const height = Math.max(this.modified.getContentHeight(), this.original.getContentHeight());
this._onDidContentSizeChange.fire({
contentHeight: height,
contentWidth: width,
contentHeightChanged: e.contentHeightChanged,
contentWidthChanged: e.contentWidthChanged
});
}));
return editor;
}
_adjustOptionsForLeftHandSide(_reader, changedOptions) {
const result = this._adjustOptionsForSubEditor(changedOptions);
if (!this._options.renderSideBySide.get()) {
// never wrap hidden editor
result.wordWrapOverride1 = 'off';
result.wordWrapOverride2 = 'off';
result.stickyScroll = { enabled: false };
// Disable unicode highlighting for the original side in inline mode, as they are not shown anyway.
result.unicodeHighlight = { nonBasicASCII: false, ambiguousCharacters: false, invisibleCharacters: false };
}
else {
result.unicodeHighlight = this._options.editorOptions.get().unicodeHighlight || {};
result.wordWrapOverride1 = this._options.diffWordWrap.get();
}
result.glyphMargin = this._options.renderSideBySide.get();
if (changedOptions.originalAriaLabel) {
result.ariaLabel = changedOptions.originalAriaLabel;
}
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
result.readOnly = !this._options.originalEditable.get();
result.dropIntoEditor = { enabled: !result.readOnly };
result.extraEditorClassName = 'original-in-monaco-diff-editor';
return result;
}
_adjustOptionsForRightHandSide(reader, changedOptions) {
const result = this._adjustOptionsForSubEditor(changedOptions);
if (changedOptions.modifiedAriaLabel) {
result.ariaLabel = changedOptions.modifiedAriaLabel;
}
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
result.wordWrapOverride1 = this._options.diffWordWrap.get();
result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + OverviewRulerFeature.ENTIRE_DIFF_OVERVIEW_WIDTH;
result.scrollbar.verticalHasArrows = false;
result.extraEditorClassName = 'modified-in-monaco-diff-editor';
return result;
}
_adjustOptionsForSubEditor(options) {
const clonedOptions = {
...options,
dimension: {
height: 0,
width: 0
},
};
clonedOptions.inDiffEditor = true;
clonedOptions.automaticLayout = false;
// Clone scrollbar options before changing them
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
clonedOptions.folding = false;
clonedOptions.codeLens = this._options.diffCodeLens.get();
clonedOptions.fixedOverflowWidgets = true;
// Clone minimap options before changing them
clonedOptions.minimap = { ...(clonedOptions.minimap || {}) };
clonedOptions.minimap.enabled = false;
if (this._options.hideUnchangedRegions.get()) {
clonedOptions.stickyScroll = { enabled: false };
}
else {
clonedOptions.stickyScroll = this._options.editorOptions.get().stickyScroll;
}
return clonedOptions;
}
_updateAriaLabel(ariaLabel) {
if (!ariaLabel) {
ariaLabel = '';
}
const ariaNavigationTip = localize('diff-aria-navigation-tip', ' use {0} to open the accessibility help.', this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getAriaLabel());
if (this._options.accessibilityVerbose.get()) {
return ariaLabel + ariaNavigationTip;
}
else if (ariaLabel) {
return ariaLabel.replaceAll(ariaNavigationTip, '');
}
return '';
}
};
DiffEditorEditors = __decorate([
__param(5, IInstantiationService),
__param(6, IKeybindingService)
], DiffEditorEditors);
export { DiffEditorEditors };
@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Sash } from '../../../../../base/browser/ui/sash/sash.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { autorun, observableValue } from '../../../../../base/common/observable.js';
import { derivedWithSetter } from '../../../../../base/common/observableInternal/derived.js';
export class SashLayout {
resetSash() {
this._sashRatio.set(undefined, undefined);
}
constructor(_options, dimensions) {
this._options = _options;
this.dimensions = dimensions;
this.sashLeft = derivedWithSetter(this, reader => {
const ratio = this._sashRatio.read(reader) ?? this._options.splitViewDefaultRatio.read(reader);
return this._computeSashLeft(ratio, reader);
}, (value, tx) => {
const contentWidth = this.dimensions.width.get();
this._sashRatio.set(value / contentWidth, tx);
});
this._sashRatio = observableValue(this, undefined);
}
/** @pure */
_computeSashLeft(desiredRatio, reader) {
const contentWidth = this.dimensions.width.read(reader);
const midPoint = Math.floor(this._options.splitViewDefaultRatio.read(reader) * contentWidth);
const sashLeft = this._options.enableSplitViewResizing.read(reader) ? Math.floor(desiredRatio * contentWidth) : midPoint;
const MINIMUM_EDITOR_WIDTH = 100;
if (contentWidth <= MINIMUM_EDITOR_WIDTH * 2) {
return midPoint;
}
if (sashLeft < MINIMUM_EDITOR_WIDTH) {
return MINIMUM_EDITOR_WIDTH;
}
if (sashLeft > contentWidth - MINIMUM_EDITOR_WIDTH) {
return contentWidth - MINIMUM_EDITOR_WIDTH;
}
return sashLeft;
}
}
export class DiffEditorSash extends Disposable {
constructor(_domNode, _dimensions, _enabled, _boundarySashes, sashLeft, _resetSash) {
super();
this._domNode = _domNode;
this._dimensions = _dimensions;
this._enabled = _enabled;
this._boundarySashes = _boundarySashes;
this.sashLeft = sashLeft;
this._resetSash = _resetSash;
this._sash = this._register(new Sash(this._domNode, {
getVerticalSashTop: (_sash) => 0,
getVerticalSashLeft: (_sash) => this.sashLeft.get(),
getVerticalSashHeight: (_sash) => this._dimensions.height.get(),
}, { orientation: 0 /* Orientation.VERTICAL */ }));
this._startSashPosition = undefined;
this._register(this._sash.onDidStart(() => {
this._startSashPosition = this.sashLeft.get();
}));
this._register(this._sash.onDidChange((e) => {
this.sashLeft.set(this._startSashPosition + (e.currentX - e.startX), undefined);
}));
this._register(this._sash.onDidEnd(() => this._sash.layout()));
this._register(this._sash.onDidReset(() => this._resetSash()));
this._register(autorun(reader => {
const sashes = this._boundarySashes.read(reader);
if (sashes) {
this._sash.orthogonalEndSash = sashes.bottom;
}
}));
this._register(autorun(reader => {
/** @description DiffEditorSash.layoutSash */
const enabled = this._enabled.read(reader);
this._sash.state = enabled ? 3 /* SashState.Enabled */ : 0 /* SashState.Disabled */;
this.sashLeft.read(reader);
this._dimensions.height.read(reader);
this._sash.layout();
}));
}
}

Some files were not shown because too many files have changed in this diff Show More