Merge branch 'master' into handle-delete-composition-helper

This commit is contained in:
Daniel Imms
2022-09-07 09:39:54 -07:00
committed by GitHub
59 changed files with 1340 additions and 547 deletions
+111
View File
@@ -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
View File
@@ -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;
}
+3 -3
View File
@@ -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';
@@ -141,7 +141,7 @@ export class MockTerminal implements ITerminal {
public renderer!: IRenderer;
public linkifier2!: ILinkifier2;
public isFocused!: boolean;
public options: ITerminalOptions = {};
public options!: Required<ITerminalOptions>;
public element!: HTMLElement;
public screenElement!: HTMLElement;
public rowContainer!: HTMLElement;
@@ -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');
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal {
browser: IBrowser;
buffer: IBuffer;
viewport: IViewport | undefined;
options: ITerminalOptions;
options: Required<ITerminalOptions>;
linkifier2: ILinkifier2;
onBlur: IEvent<void>;
@@ -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.
-14
View File
@@ -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 -3
View File
@@ -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';
@@ -24,7 +24,7 @@ export class Terminal implements ITerminalApi {
private _addonManager: AddonManager;
private _parser: IParser | undefined;
private _buffer: BufferNamespaceApi | undefined;
private _publicOptions: ITerminalOptions;
private _publicOptions: Required<ITerminalOptions>;
constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
@@ -123,7 +123,7 @@ export class Terminal implements ITerminalApi {
wraparoundMode: m.wraparound
};
}
public get options(): ITerminalOptions {
public get options(): Required<ITerminalOptions> {
return this._publicOptions;
}
public set options(options: ITerminalOptions) {
+1 -1
View File
@@ -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';
+20 -4
View File
@@ -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])
};
}
}
+1 -1
View File
@@ -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';
+2 -4
View File
@@ -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');