Merge remote-tracking branch 'upstream/master' into pr/silamon/4004
This commit is contained in:
@@ -5,35 +5,30 @@
|
||||
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { TwoKeyMap } from 'common/MultiKeyMap';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
|
||||
private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
|
||||
|
||||
public clear(): void {
|
||||
this._color = {};
|
||||
this._rgba = {};
|
||||
}
|
||||
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
|
||||
private _css: TwoKeyMap</* bg */number, /* fg */number, string | null> = new TwoKeyMap();
|
||||
|
||||
public setCss(bg: number, fg: number, value: string | null): void {
|
||||
if (!this._rgba[bg]) {
|
||||
this._rgba[bg] = {};
|
||||
}
|
||||
this._rgba[bg]![fg] = value;
|
||||
this._css.set(bg, fg, value);
|
||||
}
|
||||
|
||||
public getCss(bg: number, fg: number): string | null | undefined {
|
||||
return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
|
||||
return this._css.get(bg, fg);
|
||||
}
|
||||
|
||||
public setColor(bg: number, fg: number, value: IColor | null): void {
|
||||
if (!this._color[bg]) {
|
||||
this._color[bg] = {};
|
||||
}
|
||||
this._color[bg]![fg] = value;
|
||||
this._color.set(bg, fg, value);
|
||||
}
|
||||
|
||||
public getColor(bg: number, fg: number): IColor | null | undefined {
|
||||
return this._color[bg] ? this._color[bg]![fg] : undefined;
|
||||
return this._color.get(bg, fg);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._color.clear();
|
||||
this._css.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// This file contains strings that get exported in the API so they can be localized
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
export let promptLabel = 'Terminal input';
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferRange, ILink, ILinkProvider } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';
|
||||
|
||||
export class OscLinkProvider implements ILinkProvider {
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@IOscLinkService private readonly _oscLinkService: IOscLinkService
|
||||
) {
|
||||
}
|
||||
|
||||
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
|
||||
const line = this._bufferService.buffer.lines.get(y - 1);
|
||||
if (!line) {
|
||||
callback(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const result: ILink[] = [];
|
||||
const linkHandler = this._optionsService.rawOptions.linkHandler;
|
||||
const cell = new CellData();
|
||||
const lineLength = line.getTrimmedLength();
|
||||
let currentLinkId = -1;
|
||||
let currentStart = -1;
|
||||
let finishLink = false;
|
||||
for (let x = 0; x < lineLength; x++) {
|
||||
// Minor optimization, only check for content if there isn't a link in case the link ends with
|
||||
// a null cell
|
||||
if (currentStart === -1 && !line.hasContent(x)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
line.loadCell(x, cell);
|
||||
if (cell.hasExtendedAttrs() && cell.extended.urlId) {
|
||||
if (currentStart === -1) {
|
||||
currentStart = x;
|
||||
currentLinkId = cell.extended.urlId;
|
||||
continue;
|
||||
} else {
|
||||
finishLink = cell.extended.urlId !== currentLinkId;
|
||||
}
|
||||
} else {
|
||||
if (currentStart !== -1) {
|
||||
finishLink = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (finishLink || (currentStart !== -1 && x === lineLength - 1)) {
|
||||
const text = this._oscLinkService.getLinkData(currentLinkId)?.uri;
|
||||
if (text) {
|
||||
// These ranges are 1-based
|
||||
const range: IBufferRange = {
|
||||
start: {
|
||||
x: currentStart + 1,
|
||||
y
|
||||
},
|
||||
end: {
|
||||
// Offset end x if it's a link that ends on the last cell in the line
|
||||
x: x + (!finishLink && x === lineLength - 1 ? 1 : 0),
|
||||
y
|
||||
}
|
||||
};
|
||||
// OSC links always use underline and pointer decorations
|
||||
result.push({
|
||||
text,
|
||||
range,
|
||||
activate: (e, text) => (linkHandler ? linkHandler.activate(e, text, range) : defaultActivate(e, text)),
|
||||
hover: (e, text) => linkHandler?.hover?.(e, text, range),
|
||||
leave: (e, text) => linkHandler?.leave?.(e, text, range)
|
||||
});
|
||||
}
|
||||
finishLink = false;
|
||||
|
||||
// Clear link or start a new link if one starts immediately
|
||||
if (cell.hasExtendedAttrs() && cell.extended.urlId) {
|
||||
currentStart = x;
|
||||
currentLinkId = cell.extended.urlId;
|
||||
} else {
|
||||
currentStart = -1;
|
||||
currentLinkId = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle fetching and returning other link ranges to underline other links with the same id
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
function defaultActivate(e: MouseEvent, uri: string): void {
|
||||
const answer = confirm(`Do you want to navigate to ${uri}?`);
|
||||
if (answer) {
|
||||
const newWindow = window.open();
|
||||
if (newWindow) {
|
||||
try {
|
||||
newWindow.opener = null;
|
||||
} catch {
|
||||
// no-op, Electron can throw
|
||||
}
|
||||
newWindow.location.href = uri;
|
||||
} else {
|
||||
console.warn('Opening link blocked as opener could not be cleared');
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-23
@@ -55,6 +55,7 @@ import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRe
|
||||
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
|
||||
import { DecorationService } from 'common/services/DecorationService';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
import { OscLinkProvider } from 'browser/OscLinkProvider';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -163,6 +164,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._setup();
|
||||
|
||||
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
||||
this.linkifier2.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
|
||||
this._decorationService = this._instantiationService.createInstance(DecorationService);
|
||||
this._instantiationService.setService(IDecorationService, this._decorationService);
|
||||
|
||||
@@ -211,22 +213,20 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
acc = 'ansi';
|
||||
ident = '4;' + req.index;
|
||||
}
|
||||
if (acc) {
|
||||
switch (req.type) {
|
||||
case ColorRequestType.REPORT:
|
||||
const channels = color.toColorRGB(acc === 'ansi'
|
||||
? this._colorManager.colors.ansi[req.index]
|
||||
: this._colorManager.colors[acc]);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
|
||||
break;
|
||||
case ColorRequestType.SET:
|
||||
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
|
||||
break;
|
||||
case ColorRequestType.RESTORE:
|
||||
this._colorManager.restoreColor(req.index);
|
||||
break;
|
||||
}
|
||||
switch (req.type) {
|
||||
case ColorRequestType.REPORT:
|
||||
const channels = color.toColorRGB(acc === 'ansi'
|
||||
? this._colorManager.colors.ansi[req.index]
|
||||
: this._colorManager.colors[acc]);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
|
||||
break;
|
||||
case ColorRequestType.SET:
|
||||
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
|
||||
break;
|
||||
case ColorRequestType.RESTORE:
|
||||
this._colorManager.restoreColor(req.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._renderService?.setColors(this._colorManager.colors);
|
||||
@@ -645,7 +645,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
// send event to CoreMouseService
|
||||
function sendEvent(ev: MouseEvent | WheelEvent): boolean {
|
||||
// get mouse coordinates
|
||||
const pos = self._mouseService!.getRawByteCoords(ev, self.screenElement!, self.cols, self.rows);
|
||||
const pos = self._mouseService!.getMouseReportCoords(ev, self.screenElement!);
|
||||
if (!pos) {
|
||||
return false;
|
||||
}
|
||||
@@ -699,8 +699,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
return self.coreMouseService.triggerMouseEvent({
|
||||
col: pos.x - 33, // FIXME: why -33 here?
|
||||
row: pos.y - 33,
|
||||
col: pos.col,
|
||||
row: pos.row,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
button: but,
|
||||
action,
|
||||
ctrl: ev.ctrlKey,
|
||||
@@ -1320,13 +1322,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
switch (type) {
|
||||
case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:
|
||||
const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0);
|
||||
const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0);
|
||||
const canvasWidth = this._renderService.dimensions.canvasWidth.toFixed(0);
|
||||
const canvasHeight = this._renderService.dimensions.canvasHeight.toFixed(0);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);
|
||||
break;
|
||||
case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:
|
||||
const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0);
|
||||
const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0);
|
||||
const cellWidth = this._renderService.dimensions.actualCellWidth.toFixed(0);
|
||||
const cellHeight = this._renderService.dimensions.actualCellHeight.toFixed(0);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration }
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer, IBufferRange } from 'browser/Types';
|
||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
@@ -359,7 +359,7 @@ export class MockMouseService implements IMouseService {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
|
||||
public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
// Helper objects to avoid excessive calculation and garbage collection during rendering. These are
|
||||
// static values for each render and can be accessed using the decoration position as the key.
|
||||
|
||||
@@ -48,17 +48,3 @@ export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: {clie
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets coordinates within the terminal for a particular mouse event, wrapping
|
||||
* them to the bounds of the terminal and adding 32 to both the x and y values
|
||||
* as expected by xterm.
|
||||
*/
|
||||
export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined {
|
||||
if (!coords) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// xterm sends raw bytes and starts at 32 (SP) for each.
|
||||
return { x: coords[0] + 32, y: coords[1] + 32 };
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, IMarker, IDisposable, ILocalizableStrings, ITerminalAddon, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm';
|
||||
import { Terminal as ITerminalApi, IMarker, IDisposable, ILocalizableStrings, ITerminalAddon, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, IModes, IDecorationOptions, IDecoration } from 'xterm';
|
||||
import { IBufferRange, ITerminal } from 'browser/Types';
|
||||
import { Terminal as TerminalCore } from 'browser/Terminal';
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
|
||||
@@ -349,25 +349,27 @@ const enum VectorType {
|
||||
* not been patched with powerline characters and also to get pixel perfect rendering as rendering
|
||||
* issues can occur around AA/SPAA.
|
||||
*
|
||||
* The line variants draw beyond the cell and get clipped to ensure the end of the line is not visible.
|
||||
*
|
||||
* Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
*/
|
||||
export const powerlineDefinitions: { [index: string]: IVectorShape } = {
|
||||
// Right triangle solid
|
||||
'\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL },
|
||||
'\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL, rightPadding: 2 },
|
||||
// Right triangle line
|
||||
'\u{E0B1}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.STROKE, leftPadding: window.devicePixelRatio / 2, rightPadding: window.devicePixelRatio / 2 },
|
||||
'\u{E0B1}': { d: 'M-1,-.5 L1,.5 L-1,1.5', type: VectorType.STROKE, leftPadding: 1, rightPadding: 1 },
|
||||
// Left triangle solid
|
||||
'\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL },
|
||||
'\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL, leftPadding: 2 },
|
||||
// Left triangle line
|
||||
'\u{E0B3}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.STROKE, leftPadding: window.devicePixelRatio / 2, rightPadding: window.devicePixelRatio / 2 },
|
||||
'\u{E0B3}': { d: 'M2,-.5 L0,.5 L2,1.5', type: VectorType.STROKE, leftPadding: 1, rightPadding: 1 },
|
||||
// Right semi-circle solid,
|
||||
'\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL },
|
||||
'\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 },
|
||||
// Right semi-circle line,
|
||||
'\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE, rightPadding: window.devicePixelRatio / 2 },
|
||||
'\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE, rightPadding: 1 },
|
||||
// Left semi-circle solid,
|
||||
'\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL },
|
||||
'\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 },
|
||||
// Left semi-circle line,
|
||||
'\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE, leftPadding: window.devicePixelRatio / 2 }
|
||||
'\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE, leftPadding: 1 }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -380,7 +382,8 @@ export function tryDrawCustomChar(
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
scaledCellHeight: number,
|
||||
fontSize: number
|
||||
): boolean {
|
||||
const blockElementDefinition = blockElementDefinitions[c];
|
||||
if (blockElementDefinition) {
|
||||
@@ -402,7 +405,7 @@ export function tryDrawCustomChar(
|
||||
|
||||
const powerlineDefinition = powerlineDefinitions[c];
|
||||
if (powerlineDefinition) {
|
||||
drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, fontSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -562,7 +565,7 @@ function drawBoxDrawingChar(
|
||||
if (!args[0] || !args[1]) {
|
||||
continue;
|
||||
}
|
||||
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset));
|
||||
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, true));
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
@@ -575,10 +578,13 @@ function drawPowerlineChar(
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
scaledCellHeight: number,
|
||||
fontSize: number
|
||||
): void {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = window.devicePixelRatio;
|
||||
// Scale the stroke with DPR and font size
|
||||
const cssLineWidth = fontSize / 12;
|
||||
ctx.lineWidth = window.devicePixelRatio * cssLineWidth;
|
||||
for (const instruction of charDefinition.d.split(' ')) {
|
||||
const type = instruction[0];
|
||||
const f = svgToCanvasInstructionMap[type];
|
||||
@@ -590,7 +596,16 @@ function drawPowerlineChar(
|
||||
if (!args[0] || !args[1]) {
|
||||
continue;
|
||||
}
|
||||
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, charDefinition.leftPadding, charDefinition.rightPadding));
|
||||
f(ctx, translateArgs(
|
||||
args,
|
||||
scaledCellWidth,
|
||||
scaledCellHeight,
|
||||
xOffset,
|
||||
yOffset,
|
||||
false,
|
||||
(charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),
|
||||
(charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)
|
||||
));
|
||||
}
|
||||
if (charDefinition.type === VectorType.STROKE) {
|
||||
ctx.strokeStyle = ctx.fillStyle;
|
||||
@@ -611,7 +626,7 @@ const svgToCanvasInstructionMap: { [index: string]: any } = {
|
||||
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
|
||||
};
|
||||
|
||||
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
|
||||
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, leftPadding: number = 0, rightPadding: number = 0): number[] {
|
||||
const result = args.map(e => parseFloat(e) || parseInt(e));
|
||||
|
||||
if (result.length < 2) {
|
||||
@@ -623,7 +638,7 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
|
||||
result[x] *= cellWidth - (leftPadding * window.devicePixelRatio) - (rightPadding * window.devicePixelRatio);
|
||||
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
|
||||
// line at 100% devicePixelRatio
|
||||
if (result[x] !== 0) {
|
||||
if (doClamp && result[x] !== 0) {
|
||||
result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
|
||||
}
|
||||
// Apply the cell's offset (ie. x*cellWidth)
|
||||
@@ -635,7 +650,7 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
|
||||
result[y] *= cellHeight;
|
||||
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
|
||||
// line at 100% devicePixelRatio
|
||||
if (result[y] !== 0) {
|
||||
if (doClamp && result[y] !== 0) {
|
||||
result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
|
||||
}
|
||||
// Apply the cell's offset (ie. x*cellHeight)
|
||||
|
||||
@@ -24,10 +24,12 @@ export function observeDevicePixelDimensions(element: HTMLElement, callback: (de
|
||||
return;
|
||||
}
|
||||
|
||||
callback(
|
||||
entry.devicePixelContentBoxSize[0].inlineSize,
|
||||
entry.devicePixelContentBoxSize[0].blockSize
|
||||
);
|
||||
// Fire the callback, ignore events where the dimensions are 0x0 as the canvas is likely hidden
|
||||
const width = entry.devicePixelContentBoxSize[0].inlineSize;
|
||||
const height = entry.devicePixelContentBoxSize[0].blockSize;
|
||||
if (width > 0 && height > 0) {
|
||||
callback(width, height);
|
||||
}
|
||||
});
|
||||
observer.observe(element, { box: ['device-pixel-content-box'] } as any);
|
||||
return toDisposable(() => observer?.disconnect());
|
||||
|
||||
@@ -14,11 +14,15 @@ export function isPowerlineGlyph(codepoint: number): boolean {
|
||||
// Only return true for Powerline symbols which require
|
||||
// different padding and should be excluded from minimum contrast
|
||||
// ratio standards
|
||||
return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;
|
||||
return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;
|
||||
}
|
||||
|
||||
export function isRestrictedPowerlineGlyph(codepoint: number): boolean {
|
||||
return 0xE0B0 <= codepoint && codepoint <= 0xE0B7;
|
||||
}
|
||||
|
||||
function isBoxOrBlockGlyph(codepoint: number): boolean {
|
||||
return (0x2500 <= codepoint && codepoint <= 0x259F);
|
||||
return 0x2500 <= codepoint && codepoint <= 0x259F;
|
||||
}
|
||||
|
||||
export function excludeFromContrastRatioDemands(codepoint: number): boolean {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { color } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
@@ -204,9 +204,9 @@ export class DomRendererRowFactory {
|
||||
let bgOverride: IColor | undefined;
|
||||
let fgOverride: IColor | undefined;
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, row)) {
|
||||
this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
bgColorMode = Attributes.CM_RGB;
|
||||
@@ -219,7 +219,7 @@ export class DomRendererRowFactory {
|
||||
fgOverride = d.foregroundColorRGB;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
});
|
||||
|
||||
// Apply selection foreground if applicable
|
||||
const isInSelection = this._isCellInSelection(x, row);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ICharSizeService, IRenderService, IMouseService } from './Services';
|
||||
import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
|
||||
import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';
|
||||
|
||||
export class MouseService implements IMouseService {
|
||||
public serviceBrand: undefined;
|
||||
@@ -29,8 +29,24 @@ export class MouseService implements IMouseService {
|
||||
);
|
||||
}
|
||||
|
||||
public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
|
||||
const coords = this.getCoords(event, element, colCount, rowCount);
|
||||
return getRawByteCoords(coords);
|
||||
public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
|
||||
const coords = getCoordsRelativeToElement(window, event, element);
|
||||
|
||||
// due to rounding issues in zoom states pixel values might be negative or overflow actual canvas
|
||||
// ignore those events effectively narrowing mouse area a tiny bit at the edges
|
||||
if (!this._charSizeService.hasValidSize
|
||||
|| coords[0] < 0
|
||||
|| coords[1] < 0
|
||||
|| coords[0] >= this._renderService.dimensions.canvasWidth
|
||||
|| coords[1] >= this._renderService.dimensions.canvasHeight) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
col: Math.floor(coords[0] / this._renderService.dimensions.actualCellWidth),
|
||||
row: Math.floor(coords[1] / this._renderService.dimensions.actualCellHeight),
|
||||
x: Math.floor(coords[0]),
|
||||
y: Math.floor(coords[1])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IRenderDebouncer, IRenderDebouncerWithCallback } from 'browser/Types';
|
||||
import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
|
||||
import { IColorSet, IRenderDebouncer } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
|
||||
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
|
||||
export interface ICharSizeService {
|
||||
@@ -37,7 +35,7 @@ export interface IMouseService {
|
||||
serviceBrand: undefined;
|
||||
|
||||
getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
|
||||
getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
|
||||
getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined;
|
||||
}
|
||||
|
||||
export const IRenderService = createDecorator<IRenderService>('RenderService');
|
||||
|
||||
Reference in New Issue
Block a user