mirror of
https://github.com/xtermjs/xterm.js.git
synced 2026-01-25 16:03:36 +00:00
Merge branch 'master' into 2586_dom_renderer
This commit is contained in:
commit
a19dfe608d
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@ -18,6 +18,6 @@
|
||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^3.14.0"
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
114
addons/xterm-addon-web-links/src/WebLinkProvider.ts
Normal file
114
addons/xterm-addon-web-links/src/WebLinkProvider.ts
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
|
||||
|
||||
export class WebLinkProvider implements ILinkProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _regex: RegExp,
|
||||
private readonly _handler: (event: MouseEvent, uri: string) => void
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
|
||||
callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler));
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkComputer {
|
||||
public static computeLink(position: IBufferCellPosition, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
|
||||
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
|
||||
|
||||
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal);
|
||||
|
||||
let match;
|
||||
let stringIndex = -1;
|
||||
|
||||
while ((match = rex.exec(line)) !== null) {
|
||||
const text = match[1];
|
||||
if (!text) {
|
||||
// something matched but does not comply with the given matchIndex
|
||||
// since this is most likely a bug the regex itself we simply do nothing here
|
||||
console.log('match found without corresponding matchIndex');
|
||||
break;
|
||||
}
|
||||
|
||||
// Get index, match.index is for the outer match which includes negated chars
|
||||
// therefore we cannot use match.index directly, instead we search the position
|
||||
// of the match group in text again
|
||||
// also correct regex and string search offsets for the next loop run
|
||||
stringIndex = line.indexOf(text, stringIndex + 1);
|
||||
rex.lastIndex = stringIndex + text.length;
|
||||
if (stringIndex < 0) {
|
||||
// invalid stringIndex (should not have happened)
|
||||
break;
|
||||
}
|
||||
|
||||
let endX = stringIndex + text.length + 1;
|
||||
let endY = startLineIndex + 1;
|
||||
|
||||
while (endX > terminal.cols) {
|
||||
endX -= terminal.cols;
|
||||
endY++;
|
||||
}
|
||||
|
||||
const range = {
|
||||
start: {
|
||||
x: stringIndex + 1,
|
||||
y: startLineIndex + 1
|
||||
},
|
||||
end: {
|
||||
x: endX,
|
||||
y: endY
|
||||
}
|
||||
};
|
||||
|
||||
return { range, text, activate: handler };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entire line for the buffer line
|
||||
* @param line The line being translated.
|
||||
* @param trimRight Whether to trim whitespace to the right.
|
||||
* @param terminal The terminal
|
||||
*/
|
||||
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
|
||||
let lineString = '';
|
||||
let lineWrapsToNext: boolean;
|
||||
let prevLinesToWrap: boolean;
|
||||
|
||||
do {
|
||||
const line = terminal.buffer.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.isWrapped) {
|
||||
lineIndex--;
|
||||
}
|
||||
|
||||
prevLinesToWrap = line.isWrapped;
|
||||
} while (prevLinesToWrap);
|
||||
|
||||
const startLineIndex = lineIndex;
|
||||
|
||||
do {
|
||||
const nextLine = terminal.buffer.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
const line = terminal.buffer.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
|
||||
return [lineString, startLineIndex];
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@ const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
before(async function (): Promise<any> {
|
||||
this.timeout(10000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
@ -29,29 +29,29 @@ describe('WebLinksAddon', () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
beforeEach(async function (): Promise<any> {
|
||||
this.timeout(5000);
|
||||
await page.goto(APP);
|
||||
});
|
||||
|
||||
it('.com', async function(): Promise<any> {
|
||||
it('.com', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com');
|
||||
});
|
||||
|
||||
it('.com.au', async function(): Promise<any> {
|
||||
it('.com.au', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com.au');
|
||||
});
|
||||
|
||||
it('.io', async function(): Promise<any> {
|
||||
it('.io', async function (): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.io');
|
||||
});
|
||||
});
|
||||
|
||||
async function testHostName(hostname: string): Promise<void> {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await openTerminal({ rendererType: 'dom', cols: 40 });
|
||||
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
|
||||
const data = ` http://${hostname} \\r\\n` +
|
||||
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, ILinkProvider, IDisposable } from 'xterm';
|
||||
import { WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
@ -38,22 +39,32 @@ function handleLink(event: MouseEvent, uri: string): void {
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
private _linkMatcherId: number | undefined;
|
||||
private _terminal: Terminal | undefined;
|
||||
private _linkProvider: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
|
||||
private _options: ILinkMatcherOptions = {}
|
||||
private _options: ILinkMatcherOptions = {},
|
||||
private _useLinkProvider: boolean = false
|
||||
) {
|
||||
this._options.matchIndex = 1;
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
|
||||
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
|
||||
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
|
||||
} else {
|
||||
// TODO: This should be removed eventually
|
||||
this._linkMatcherId = (<Terminal>this._terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
|
||||
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
|
||||
}
|
||||
|
||||
this._linkProvider?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,12 @@ declare module 'xterm-addon-web-links' {
|
||||
* Creates a new web links addon.
|
||||
* @param handler The callback when the link is called.
|
||||
* @param options Options for the link matcher.
|
||||
* @param useLinkProvider Whether to use the new link provider API to create
|
||||
* the links. This is an option because use of both link matcher (old) and
|
||||
* link provider (new) may cause issues. Link provider will eventually be
|
||||
* the default and only option.
|
||||
*/
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
|
||||
@ -18,6 +18,9 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
super(container, 'link', zIndex, true, colors);
|
||||
terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
|
||||
terminal.linkifier2.onLinkHover(e => this._onLinkHover(e));
|
||||
terminal.linkifier2.onLinkLeave(e => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public resize(terminal: Terminal, dim: IRenderDimensions): void {
|
||||
|
||||
@ -152,7 +152,8 @@ function createTerminal(): void {
|
||||
addons.serialize.instance = new SerializeAddon();
|
||||
addons.fit.instance = new FitAddon();
|
||||
addons.unicode11.instance = new Unicode11Addon();
|
||||
addons['web-links'].instance = new WebLinksAddon();
|
||||
// TODO: Remove arguments when link provider API is the default
|
||||
addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true);
|
||||
typedTerm.loadAddon(addons.fit.instance);
|
||||
typedTerm.loadAddon(addons.search.instance);
|
||||
typedTerm.loadAddon(addons.serialize.instance);
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<div id="terminal-container"></div>
|
||||
<div>
|
||||
<h3>Options</h3>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
|
||||
<div id="options-container"></div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings';
|
||||
import { SoundService } from 'browser/services/SoundService';
|
||||
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
@ -57,11 +57,12 @@ import { MouseService } from 'browser/services/MouseService';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { LogService } from 'common/services/LogService';
|
||||
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
|
||||
import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
import { WriteBuffer } from 'common/input/WriteBuffer';
|
||||
import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { UnicodeService } from 'common/services/UnicodeService';
|
||||
import { CharsetService } from 'common/services/CharsetService';
|
||||
@ -131,6 +132,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _inputHandler: InputHandler;
|
||||
public linkifier: ILinkifier;
|
||||
public linkifier2: ILinkifier2;
|
||||
public viewport: IViewport;
|
||||
private _compositionHelper: ICompositionHelper;
|
||||
private _mouseZoneManager: IMouseZoneManager;
|
||||
@ -228,7 +230,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._windowsMode = undefined;
|
||||
this._renderService?.dispose();
|
||||
this._customKeyEventHandler = null;
|
||||
this.write = () => {};
|
||||
this.write = () => { };
|
||||
this.element?.parentNode?.removeChild(this.element);
|
||||
}
|
||||
|
||||
@ -257,6 +259,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
if (!this.linkifier) {
|
||||
this.linkifier = new Linkifier(this._bufferService, this._logService, this.optionsService, this.unicodeService);
|
||||
}
|
||||
if (!this.linkifier2) {
|
||||
this.linkifier2 = new Linkifier2(this._bufferService);
|
||||
}
|
||||
|
||||
if (this.options.windowsMode) {
|
||||
this._enableWindowsMode();
|
||||
@ -585,6 +590,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this.register(this._mouseZoneManager);
|
||||
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
|
||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||
this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService);
|
||||
|
||||
// This event listener must be registered aftre MouseZoneManager is created
|
||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
|
||||
@ -619,8 +625,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _createRenderer(): IRenderer {
|
||||
switch (this.options.rendererType) {
|
||||
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier);
|
||||
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier);
|
||||
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier, this.linkifier2);
|
||||
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier, this.linkifier2);
|
||||
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
|
||||
}
|
||||
}
|
||||
@ -729,13 +735,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
* Note: 'mousedown' currently is "always on" and not managed
|
||||
* by onProtocolChange.
|
||||
*/
|
||||
const requestedEvents: {[key: string]: ((ev: Event) => void) | null} = {
|
||||
const requestedEvents: { [key: string]: ((ev: Event) => void) | null } = {
|
||||
mouseup: null,
|
||||
wheel: null,
|
||||
mousedrag: null,
|
||||
mousemove: null
|
||||
};
|
||||
const eventListeners: {[key: string]: (ev: Event) => void} = {
|
||||
const eventListeners: { [key: string]: (ev: Event) => void } = {
|
||||
mouseup: (ev: MouseEvent) => {
|
||||
sendEvent(ev);
|
||||
if (!ev.buttons) {
|
||||
@ -858,7 +864,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
|
||||
// Construct and send sequences
|
||||
const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
|
||||
const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
|
||||
let data = '';
|
||||
for (let i = 0; i < Math.abs(amount); i++) {
|
||||
data += sequence;
|
||||
@ -1121,6 +1127,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
}
|
||||
}
|
||||
|
||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
return this.linkifier2.registerLinkProvider(linkProvider);
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
||||
const joinerId = this._renderService.registerCharacterJoiner(handler);
|
||||
this.refresh(0, this.rows - 1);
|
||||
@ -1273,8 +1283,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
|
||||
private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
|
||||
@ -9,10 +9,10 @@ import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
|
||||
import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { Terminal } from './Terminal';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
|
||||
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types';
|
||||
import { IOptionsService, IUnicodeService } from 'common/services/Services';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
@ -94,6 +94,9 @@ export class MockTerminal implements ITerminal {
|
||||
deregisterLinkMatcher(matcherId: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
hasSelection(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@ -136,6 +139,7 @@ export class MockTerminal implements ITerminal {
|
||||
bracketedPasteMode: boolean;
|
||||
renderer: IRenderer;
|
||||
linkifier: ILinkifier;
|
||||
linkifier2: ILinkifier2;
|
||||
isFocused: boolean;
|
||||
options: ITerminalOptions = {};
|
||||
element: HTMLElement;
|
||||
@ -300,16 +304,16 @@ export class MockRenderer implements IRenderer {
|
||||
setColors(colors: IColorSet): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onResize(cols: number, rows: number): void {}
|
||||
onCharSizeChanged(): void {}
|
||||
onBlur(): void {}
|
||||
onFocus(): void {}
|
||||
onSelectionChanged(start: [number, number], end: [number, number]): void {}
|
||||
onCursorMove(): void {}
|
||||
onOptionsChanged(): void {}
|
||||
onDevicePixelRatioChange(): void {}
|
||||
clear(): void {}
|
||||
renderRows(start: number, end: number): void {}
|
||||
onResize(cols: number, rows: number): void { }
|
||||
onCharSizeChanged(): void { }
|
||||
onBlur(): void { }
|
||||
onFocus(): void { }
|
||||
onSelectionChanged(start: [number, number], end: [number, number]): void { }
|
||||
onCursorMove(): void { }
|
||||
onOptionsChanged(): void { }
|
||||
onDevicePixelRatioChange(): void { }
|
||||
clear(): void { }
|
||||
renderRows(start: number, end: number): void { }
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(): boolean { return true; }
|
||||
}
|
||||
|
||||
6
src/Types.d.ts
vendored
6
src/Types.d.ts
vendored
@ -3,10 +3,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types';
|
||||
import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
|
||||
import { IOptionsService, IUnicodeService } from 'common/services/Services';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
@ -179,6 +179,7 @@ export interface IPublicTerminal extends IDisposable {
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): void;
|
||||
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
|
||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
||||
deregisterCharacterJoiner(joinerId: number): void;
|
||||
addMarker(cursorYOffset: number): IMarker;
|
||||
@ -212,6 +213,7 @@ export interface IElementAccessor {
|
||||
|
||||
export interface ILinkifierAccessor {
|
||||
linkifier: ILinkifier;
|
||||
linkifier2: ILinkifier2;
|
||||
}
|
||||
|
||||
// TODO: The options that are not in the public API should be reviewed
|
||||
|
||||
243
src/browser/Linkifier2.ts
Normal file
243
src/browser/Linkifier2.ts
Normal file
@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent } from './Types';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBufferService, ICoreService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
|
||||
export class Linkifier2 implements ILinkifier2 {
|
||||
private _element: HTMLElement | undefined;
|
||||
private _mouseService: IMouseService | undefined;
|
||||
private _renderService: IRenderService | undefined;
|
||||
private _linkProviders: ILinkProvider[] = [];
|
||||
private _currentLink: ILink | undefined;
|
||||
private _lastMouseEvent: MouseEvent | undefined;
|
||||
private _linkCacheDisposables: IDisposable[] = [];
|
||||
private _lastBufferCell: IBufferCellPosition | undefined;
|
||||
|
||||
private _onLinkHover = new EventEmitter<ILinkifierEvent>();
|
||||
public get onLinkHover(): IEvent<ILinkifierEvent> { return this._onLinkHover.event; }
|
||||
private _onLinkLeave = new EventEmitter<ILinkifierEvent>();
|
||||
public get onLinkLeave(): IEvent<ILinkifierEvent> { return this._onLinkLeave.event; }
|
||||
|
||||
constructor(
|
||||
private readonly _bufferService: IBufferService
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
this._linkProviders.push(linkProvider);
|
||||
return {
|
||||
dispose: () => {
|
||||
// Remove the link provider from the list
|
||||
const providerIndex = this._linkProviders.indexOf(linkProvider);
|
||||
|
||||
if (providerIndex !== -1) {
|
||||
this._linkProviders.splice(providerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void {
|
||||
this._element = element;
|
||||
this._mouseService = mouseService;
|
||||
this._renderService = renderService;
|
||||
|
||||
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
|
||||
this._element.addEventListener('click', this._onMouseDown.bind(this));
|
||||
}
|
||||
|
||||
private _onMouseMove(event: MouseEvent): void {
|
||||
this._lastMouseEvent = event;
|
||||
|
||||
if (!this._element || !this._mouseService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {
|
||||
this._onHover(position);
|
||||
this._lastBufferCell = position;
|
||||
}
|
||||
}
|
||||
|
||||
private _onHover(position: IBufferCellPosition): void {
|
||||
if (this._currentLink) {
|
||||
// Check the if the link is in the mouse position
|
||||
const isInPosition = this._linkAtPosition(this._currentLink, position);
|
||||
|
||||
// Check if we need to clear the link
|
||||
if (!isInPosition) {
|
||||
this._clearCurrentLink();
|
||||
this._askForLink(position);
|
||||
}
|
||||
} else {
|
||||
this._askForLink(position);
|
||||
}
|
||||
}
|
||||
|
||||
private _askForLink(position: IBufferCellPosition): void {
|
||||
const providerReplies: Map<Number, ILink | undefined> = new Map();
|
||||
let linkProvided = false;
|
||||
|
||||
// There is no link cached, so ask for one
|
||||
this._linkProviders.forEach((linkProvider, i) => {
|
||||
linkProvider.provideLink(position, (link: ILink | undefined) => {
|
||||
providerReplies.set(i, link);
|
||||
|
||||
// Check if every provider before this one has come back undefined
|
||||
let hasLinkBefore = false;
|
||||
for (let j = 0; j < i; j++) {
|
||||
if (!providerReplies.has(j) || providerReplies.get(j)) {
|
||||
hasLinkBefore = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If all providers with higher priority came back undefined, then this link should be used
|
||||
if (!hasLinkBefore && link) {
|
||||
linkProvided = true;
|
||||
this._handleNewLink(link);
|
||||
}
|
||||
|
||||
// Check if all the providers have responded
|
||||
if (providerReplies.size === this._linkProviders.length && !linkProvided) {
|
||||
// Respect the order of the link providers
|
||||
for (let j = 0; j < providerReplies.size; j++) {
|
||||
const currentLink = providerReplies.get(j);
|
||||
if (currentLink) {
|
||||
this._handleNewLink(currentLink);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _onMouseDown(event: MouseEvent): void {
|
||||
if (!this._element || !this._mouseService || !this._currentLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._linkAtPosition(this._currentLink, position)) {
|
||||
this._currentLink.activate(event, this._currentLink.text);
|
||||
}
|
||||
}
|
||||
|
||||
private _clearCurrentLink(startRow?: number, endRow?: number): void {
|
||||
if (!this._element || !this._currentLink || !this._lastMouseEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a start and end row, check that the link is within it
|
||||
if (!startRow || !endRow || (this._currentLink.range.start.y >= startRow && this._currentLink.range.end.y <= endRow)) {
|
||||
this._linkLeave(this._element, this._currentLink, this._lastMouseEvent);
|
||||
this._currentLink = undefined;
|
||||
this._linkCacheDisposables.forEach(l => l.dispose());
|
||||
this._linkCacheDisposables = [];
|
||||
}
|
||||
}
|
||||
|
||||
private _handleNewLink(link: ILink): void {
|
||||
if (!this._element || !this._lastMouseEvent || !this._mouseService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger hover if the we have a link at the position
|
||||
if (this._linkAtPosition(link, position)) {
|
||||
this._currentLink = link;
|
||||
this._linkHover(this._element, link, this._lastMouseEvent);
|
||||
|
||||
// Add listener for rerendering
|
||||
if (this._renderService) {
|
||||
this._linkCacheDisposables.push(this._renderService.onRender(e => {
|
||||
this._clearCurrentLink(e.start + 1 + this._bufferService.buffer.ydisp, e.end + 1 + this._bufferService.buffer.ydisp);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {
|
||||
const range = link.range;
|
||||
const scrollOffset = this._bufferService.buffer.ydisp;
|
||||
|
||||
this._onLinkHover.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
|
||||
element.classList.add('xterm-cursor-pointer');
|
||||
|
||||
if (link.hover) {
|
||||
link.hover(event, link.text);
|
||||
}
|
||||
}
|
||||
|
||||
private _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
|
||||
const range = link.range;
|
||||
const scrollOffset = this._bufferService.buffer.ydisp;
|
||||
|
||||
this._onLinkLeave.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x - 1, range.end.y - scrollOffset - 1, undefined));
|
||||
element.classList.remove('xterm-cursor-pointer');
|
||||
|
||||
if (link.leave) {
|
||||
link.leave(event, link.text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the buffer position is within the link
|
||||
* @param link
|
||||
* @param position
|
||||
*/
|
||||
private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {
|
||||
const sameLine = link.range.start.y === link.range.end.y;
|
||||
const wrappedFromLeft = link.range.start.y < position.y;
|
||||
const wrappedToRight = link.range.end.y > position.y;
|
||||
|
||||
// If the start and end have the same y, then the position must be between start and end x
|
||||
// If not, then handle each case seperately, depending on which way it wraps
|
||||
return ((sameLine && link.range.start.x <= position.x && link.range.end.x >= position.x) ||
|
||||
(wrappedFromLeft && link.range.end.x >= position.x) ||
|
||||
(wrappedToRight && link.range.start.x <= position.x) ||
|
||||
(wrappedFromLeft && wrappedToRight)) &&
|
||||
link.range.start.y <= position.y &&
|
||||
link.range.end.y >= position.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffer position from a mouse event
|
||||
* @param event
|
||||
*/
|
||||
private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement, mouseService: IMouseService): IBufferCellPosition | undefined {
|
||||
const coords = mouseService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);
|
||||
if (!coords) {
|
||||
return;
|
||||
}
|
||||
|
||||
return { x: coords[0], y: coords[1] + this._bufferService.buffer.ydisp };
|
||||
}
|
||||
|
||||
private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {
|
||||
return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };
|
||||
}
|
||||
}
|
||||
31
src/browser/Types.d.ts
vendored
31
src/browser/Types.d.ts
vendored
@ -5,6 +5,7 @@
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
|
||||
export interface IColorManager {
|
||||
colors: IColorSet;
|
||||
@ -105,6 +106,14 @@ export interface ILinkifier {
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
|
||||
export interface ILinkifier2 {
|
||||
onLinkHover: IEvent<ILinkifierEvent>;
|
||||
onLinkLeave: IEvent<ILinkifierEvent>;
|
||||
|
||||
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
|
||||
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
|
||||
}
|
||||
|
||||
export interface ILinkMatcherOptions {
|
||||
/**
|
||||
* The index of the link from the regex.match(text) call. This defaults to 0
|
||||
@ -155,3 +164,25 @@ export interface IMouseZone {
|
||||
leaveCallback: () => any | undefined;
|
||||
willLinkActivate: (e: MouseEvent) => boolean;
|
||||
}
|
||||
|
||||
interface ILinkProvider {
|
||||
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
|
||||
}
|
||||
|
||||
interface ILink {
|
||||
range: IBufferRange;
|
||||
text: string;
|
||||
activate(event: MouseEvent, text: string): void;
|
||||
hover?(event: MouseEvent, text: string): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
}
|
||||
|
||||
interface IBufferRange {
|
||||
start: IBufferCellPosition;
|
||||
end: IBufferCellPosition;
|
||||
}
|
||||
|
||||
interface IBufferCellPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private _bubbleScroll(ev: Event, amount: number): boolean {
|
||||
const scrollPosFromTop = this._viewportElement.scrollTop + this._lastRecordedViewportHeight;
|
||||
if ((amount < 0 && this._viewportElement.scrollTop !== 0) ||
|
||||
(amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
|
||||
(amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
|
||||
if (ev.cancelable) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
@ -235,8 +235,8 @@ export class Viewport extends Disposable implements IViewport {
|
||||
const modifier = this._optionsService.options.fastScrollModifier;
|
||||
// Multiply the scroll speed when the modifier is down
|
||||
if ((modifier === 'alt' && ev.altKey) ||
|
||||
(modifier === 'ctrl' && ev.ctrlKey) ||
|
||||
(modifier === 'shift' && ev.shiftKey)) {
|
||||
(modifier === 'ctrl' && ev.ctrlKey) ||
|
||||
(modifier === 'shift' && ev.shiftKey)) {
|
||||
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
@ -19,12 +19,16 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
colors: IColorSet,
|
||||
rendererId: number,
|
||||
linkifier: ILinkifier,
|
||||
linkifier2: ILinkifier2,
|
||||
readonly bufferService: IBufferService,
|
||||
readonly optionsService: IOptionsService
|
||||
) {
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
|
||||
linkifier2.onLinkHover(e => this._onLinkHover(e));
|
||||
linkifier2.onLinkLeave(e => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
|
||||
@ -10,7 +10,7 @@ import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICh
|
||||
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
|
||||
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifier } from 'browser/Types';
|
||||
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
@ -33,7 +33,8 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
constructor(
|
||||
private _colors: IColorSet,
|
||||
private readonly _screenElement: HTMLElement,
|
||||
private readonly _linkifier: ILinkifier,
|
||||
readonly linkifier: ILinkifier,
|
||||
readonly linkifier2: ILinkifier2,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ -47,7 +48,7 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
this._renderLayers = [
|
||||
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
|
||||
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
|
||||
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, this._linkifier, this._bufferService, _optionsService),
|
||||
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier, linkifier2, this._bufferService, _optionsService),
|
||||
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService)
|
||||
];
|
||||
this.dimensions = {
|
||||
|
||||
@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRo
|
||||
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
@ -48,6 +48,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
private readonly _screenElement: HTMLElement,
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _linkifier: ILinkifier,
|
||||
private readonly _linkifier2: ILinkifier2,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
@ -88,6 +89,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
this._linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
|
||||
this._linkifier2.onLinkHover(e => this._onLinkHover(e));
|
||||
this._linkifier2.onLinkLeave(e => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@ -127,12 +131,12 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
const styles =
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
|
||||
` display: inline-block;` +
|
||||
` height: 100%;` +
|
||||
` vertical-align: top;` +
|
||||
` width: ${this.dimensions.actualCellWidth}px` +
|
||||
`}`;
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
|
||||
` display: inline-block;` +
|
||||
` height: 100%;` +
|
||||
` vertical-align: top;` +
|
||||
` width: ${this.dimensions.actualCellWidth}px` +
|
||||
`}`;
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
|
||||
@ -154,84 +158,84 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Base CSS
|
||||
let styles =
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
|
||||
` color: ${this._colors.foreground.css};` +
|
||||
` font-family: ${this._optionsService.options.fontFamily};` +
|
||||
` font-size: ${this._optionsService.options.fontSize}px;` +
|
||||
`}`;
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
|
||||
` color: ${this._colors.foreground.css};` +
|
||||
` font-family: ${this._optionsService.options.fontFamily};` +
|
||||
` font-size: ${this._optionsService.options.fontSize}px;` +
|
||||
`}`;
|
||||
// Text styles
|
||||
styles +=
|
||||
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
|
||||
` font-weight: ${this._optionsService.options.fontWeight};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${BOLD_CLASS} {` +
|
||||
` font-weight: ${this._optionsService.options.fontWeightBold};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
|
||||
` font-style: italic;` +
|
||||
`}`;
|
||||
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
|
||||
` font-weight: ${this._optionsService.options.fontWeight};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${BOLD_CLASS} {` +
|
||||
` font-weight: ${this._optionsService.options.fontWeightBold};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
|
||||
` font-style: italic;` +
|
||||
`}`;
|
||||
// Blink animation
|
||||
styles +=
|
||||
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
|
||||
` 50% {` +
|
||||
` box-shadow: none;` +
|
||||
` }` +
|
||||
`}`;
|
||||
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
|
||||
` 50% {` +
|
||||
` box-shadow: none;` +
|
||||
` }` +
|
||||
`}`;
|
||||
styles +=
|
||||
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
|
||||
` 0% {` +
|
||||
` background-color: ${this._colors.cursor.css};` +
|
||||
` color: ${this._colors.cursorAccent.css};` +
|
||||
` }` +
|
||||
` 50% {` +
|
||||
` background-color: ${this._colors.cursorAccent.css};` +
|
||||
` color: ${this._colors.cursor.css};` +
|
||||
` }` +
|
||||
`}`;
|
||||
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
|
||||
` 0% {` +
|
||||
` background-color: ${this._colors.cursor.css};` +
|
||||
` color: ${this._colors.cursorAccent.css};` +
|
||||
` }` +
|
||||
` 50% {` +
|
||||
` background-color: ${this._colors.cursorAccent.css};` +
|
||||
` color: ${this._colors.cursor.css};` +
|
||||
` }` +
|
||||
`}`;
|
||||
// Cursor
|
||||
styles +=
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` outline: 1px solid ${this._colors.cursor.css};` +
|
||||
` outline-offset: -1px;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
|
||||
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` background-color: ${this._colors.cursor.css};` +
|
||||
` color: ${this._colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
|
||||
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
||||
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
|
||||
`}`;
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` outline: 1px solid ${this._colors.cursor.css};` +
|
||||
` outline-offset: -1px;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
|
||||
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` background-color: ${this._colors.cursor.css};` +
|
||||
` color: ${this._colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
|
||||
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
||||
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
|
||||
`}`;
|
||||
// Selection
|
||||
styles +=
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} {` +
|
||||
` position: absolute;` +
|
||||
` top: 0;` +
|
||||
` left: 0;` +
|
||||
` z-index: 1;` +
|
||||
` pointer-events: none;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
||||
` position: absolute;` +
|
||||
` background-color: ${this._colors.selection.css};` +
|
||||
`}`;
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} {` +
|
||||
` position: absolute;` +
|
||||
` top: 0;` +
|
||||
` left: 0;` +
|
||||
` z-index: 1;` +
|
||||
` pointer-events: none;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
||||
` position: absolute;` +
|
||||
` background-color: ${this._colors.selection.css};` +
|
||||
`}`;
|
||||
// Colors
|
||||
this._colors.ansi.forEach((c, i) => {
|
||||
styles +=
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
||||
});
|
||||
styles +=
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
|
||||
|
||||
this._themeStyleElement.innerHTML = styles;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
|
||||
import { ITerminal } from '../Types';
|
||||
import { IBufferLine, ICellData } from 'common/Types';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
@ -72,6 +72,9 @@ export class Terminal implements ITerminalApi {
|
||||
public deregisterLinkMatcher(matcherId: number): void {
|
||||
this._core.deregisterLinkMatcher(matcherId);
|
||||
}
|
||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
return this._core.registerLinkProvider(linkProvider);
|
||||
}
|
||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
||||
return this._core.registerCharacterJoiner(handler);
|
||||
}
|
||||
@ -229,7 +232,7 @@ class BufferLineApiView implements IBufferLineApi {
|
||||
}
|
||||
|
||||
class ParserApi implements IParser {
|
||||
constructor(private _core: ITerminal) {}
|
||||
constructor(private _core: ITerminal) { }
|
||||
|
||||
public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
|
||||
@ -258,7 +261,7 @@ class ParserApi implements IParser {
|
||||
}
|
||||
|
||||
class UnicodeApi implements IUnicodeHandling {
|
||||
constructor(private _core: ITerminal) {}
|
||||
constructor(private _core: ITerminal) { }
|
||||
|
||||
public register(provider: IUnicodeVersionProvider): void {
|
||||
this._core.unicodeService.register(provider);
|
||||
|
||||
@ -524,6 +524,142 @@ describe('API Integration Tests', function(): void {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
|
||||
});
|
||||
|
||||
describe('registerLinkProvider', () => {
|
||||
it('should fire provideLink when hovering cells', async () => {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
provideLink: (position, cb) => {
|
||||
calls.push(position);
|
||||
cb(undefined);
|
||||
}
|
||||
});
|
||||
`);
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 1, 1);
|
||||
await moveMouseCell(page, dims, 2, 2);
|
||||
await moveMouseCell(page, dims, 10, 4);
|
||||
await pollFor(page, `window.calls`, [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 10, y: 4 }]);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
|
||||
it('should fire hover and leave events on the link', async () => {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
provideLink: (position, cb) => {
|
||||
window.calls.push('provide ' + position.x + ',' + position.y);
|
||||
if (position.x >= 5 && position.x <= 7 && position.y === 1) {
|
||||
window.calls.push('match');
|
||||
cb({
|
||||
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
|
||||
text: 'bar',
|
||||
activate: () => window.calls.push('activate'),
|
||||
hover: () => window.calls.push('hover'),
|
||||
leave: () => window.calls.push('leave')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
`);
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 5, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover']);
|
||||
await moveMouseCell(page, dims, 4, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1']);
|
||||
await moveMouseCell(page, dims, 7, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover']);
|
||||
await moveMouseCell(page, dims, 8, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover', 'leave', 'provide 8,1']);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
|
||||
it('should work fine when hover and leave callbacks are not provided', async () => {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
provideLink: (position, cb) => {
|
||||
window.calls.push('provide ' + position.x + ',' + position.y);
|
||||
if (position.x >= 5 && position.x <= 7 && position.y === 1) {
|
||||
window.calls.push('match');
|
||||
cb({
|
||||
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
|
||||
text: 'bar',
|
||||
activate: () => window.calls.push('activate')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
`);
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 5, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match']);
|
||||
await moveMouseCell(page, dims, 4, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1']);
|
||||
await moveMouseCell(page, dims, 7, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match']);
|
||||
await moveMouseCell(page, dims, 8, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match', 'provide 8,1']);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
|
||||
it('should fire activate events when clicking the link', async () => {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await writeSync(page, 'a b c');
|
||||
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');
|
||||
|
||||
// Focus terminal to avoid a render event clearing the active link
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 5, 5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await timeout(50); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help
|
||||
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
provideLink: (position, cb) => {
|
||||
window.calls.push('provide ' + position.x + ',' + position.y);
|
||||
cb({
|
||||
range: { start: position, end: position },
|
||||
text: window.term.buffer.getLine(position.y - 1).getCell(position.x - 1).getChars(),
|
||||
activate: (_, text) => window.calls.push('activate ' + text),
|
||||
hover: () => window.calls.push('hover'),
|
||||
leave: () => window.calls.push('leave')
|
||||
});
|
||||
}
|
||||
});
|
||||
`);
|
||||
await moveMouseCell(page, dims, 3, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover']);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b']);
|
||||
await moveMouseCell(page, dims, 1, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover']);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a']);
|
||||
await moveMouseCell(page, dims, 5, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover']);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover', 'activate c']);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
@ -535,3 +671,49 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
interface IDimensions {
|
||||
top: number;
|
||||
left: number;
|
||||
renderDimensions: IRenderDimensions;
|
||||
}
|
||||
|
||||
interface IRenderDimensions {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
scaledCellWidth: number;
|
||||
scaledCellHeight: number;
|
||||
scaledCharLeft: number;
|
||||
scaledCharTop: number;
|
||||
scaledCanvasWidth: number;
|
||||
scaledCanvasHeight: number;
|
||||
canvasWidth: number;
|
||||
canvasHeight: number;
|
||||
actualCellWidth: number;
|
||||
actualCellHeight: number;
|
||||
}
|
||||
|
||||
async function getDimensions(): Promise<IDimensions> {
|
||||
return await page.evaluate(`
|
||||
(function() {
|
||||
const rect = document.querySelector('.xterm-rows').getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
renderDimensions: window.term._core._renderService.dimensions
|
||||
};
|
||||
})();
|
||||
`);
|
||||
}
|
||||
|
||||
async function getCellCoordinates(dimensions: IDimensions, col: number, row: number): Promise<{ x: number, y: number }> {
|
||||
return {
|
||||
x: dimensions.left + dimensions.renderDimensions.scaledCellWidth * (col - 0.5),
|
||||
y: dimensions.top + dimensions.renderDimensions.scaledCellHeight * (row - 0.5)
|
||||
};
|
||||
}
|
||||
|
||||
async function moveMouseCell(page: puppeteer.Page, dimensions: IDimensions, col: number, row: number) {
|
||||
const coords = await getCellCoordinates(dimensions, col, row);
|
||||
await page.mouse.move(coords.x, coords.y);
|
||||
}
|
||||
|
||||
141
typings/xterm.d.ts
vendored
141
typings/xterm.d.ts
vendored
@ -387,10 +387,10 @@ declare module 'xterm' {
|
||||
|
||||
/**
|
||||
* Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t).
|
||||
*
|
||||
*
|
||||
* Most settings have no default implementation, as they heavily rely on
|
||||
* the embedding environment.
|
||||
*
|
||||
*
|
||||
* To implement a feature, create a custom CSI hook like this:
|
||||
* ```ts
|
||||
* term.parser.addCsiHandler({final: 't'}, params => {
|
||||
@ -403,8 +403,8 @@ declare module 'xterm' {
|
||||
* return false; // any Ps that was not handled
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note on security:
|
||||
*
|
||||
* Note on security:
|
||||
* Most features are meant to deal with some information of the host machine
|
||||
* where the terminal runs on. This is seen as a security risk possibly leaking
|
||||
* sensitive data of the host to the program in the terminal. Therefore all options
|
||||
@ -413,18 +413,18 @@ declare module 'xterm' {
|
||||
*/
|
||||
export interface IWindowOptions {
|
||||
/**
|
||||
* Ps=1 De-iconify window.
|
||||
* Ps=1 De-iconify window.
|
||||
* No default implementation.
|
||||
*/
|
||||
restoreWin?: boolean;
|
||||
/**
|
||||
* Ps=2 Iconify window.
|
||||
* Ps=2 Iconify window.
|
||||
* No default implementation.
|
||||
*/
|
||||
minimizeWin?: boolean;
|
||||
/**
|
||||
* Ps=3 ; x ; y
|
||||
* Move window to [x, y].
|
||||
* Move window to [x, y].
|
||||
* No default implementation.
|
||||
*/
|
||||
setWinPosition?: boolean;
|
||||
@ -432,17 +432,17 @@ declare module 'xterm' {
|
||||
* Ps = 4 ; height ; width
|
||||
* Resize the window to given `height` and `width` in pixels.
|
||||
* Omitted parameters should reuse the current height or width.
|
||||
* Zero parameters should use the display's height or width.
|
||||
* Zero parameters should use the display's height or width.
|
||||
* No default implementation.
|
||||
*/
|
||||
setWinSizePixels?: boolean;
|
||||
/**
|
||||
* Ps=5 Raise the window to the front of the stacking order.
|
||||
* Ps=5 Raise the window to the front of the stacking order.
|
||||
* No default implementation.
|
||||
*/
|
||||
raiseWin?: boolean;
|
||||
/**
|
||||
* Ps=6 Lower the xterm window to the bottom of the stacking order.
|
||||
* Ps=6 Lower the xterm window to the bottom of the stacking order.
|
||||
* No default implementation.
|
||||
*/
|
||||
lowerWin?: boolean;
|
||||
@ -452,7 +452,7 @@ declare module 'xterm' {
|
||||
* Ps = 8 ; height ; width
|
||||
* Resize the text area to given height and width in characters.
|
||||
* Omitted parameters should reuse the current height or width.
|
||||
* Zero parameters use the display's height or width.
|
||||
* Zero parameters use the display's height or width.
|
||||
* No default implementation.
|
||||
*/
|
||||
setWinSizeChars?: boolean;
|
||||
@ -460,81 +460,81 @@ declare module 'xterm' {
|
||||
* Ps=9 ; 0 Restore maximized window.
|
||||
* Ps=9 ; 1 Maximize window (i.e., resize to screen size).
|
||||
* Ps=9 ; 2 Maximize window vertically.
|
||||
* Ps=9 ; 3 Maximize window horizontally.
|
||||
* Ps=9 ; 3 Maximize window horizontally.
|
||||
* No default implementation.
|
||||
*/
|
||||
maximizeWin?: boolean;
|
||||
/**
|
||||
* Ps=10 ; 0 Undo full-screen mode.
|
||||
* Ps=10 ; 1 Change to full-screen.
|
||||
* Ps=10 ; 2 Toggle full-screen.
|
||||
* Ps=10 ; 2 Toggle full-screen.
|
||||
* No default implementation.
|
||||
*/
|
||||
fullscreenWin?: boolean;
|
||||
/** Ps=11 Report xterm window state.
|
||||
* If the xterm window is non-iconified, it returns "CSI 1 t".
|
||||
* If the xterm window is iconified, it returns "CSI 2 t".
|
||||
* If the xterm window is iconified, it returns "CSI 2 t".
|
||||
* No default implementation.
|
||||
*/
|
||||
getWinState?: boolean;
|
||||
/**
|
||||
* Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t".
|
||||
* Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t".
|
||||
* Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t".
|
||||
* No default implementation.
|
||||
*/
|
||||
getWinPosition?: boolean;
|
||||
/**
|
||||
* Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t".
|
||||
* Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t".
|
||||
* Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t".
|
||||
* Has a default implementation.
|
||||
*/
|
||||
getWinSizePixels?: boolean;
|
||||
/**
|
||||
* Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t".
|
||||
* Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t".
|
||||
* No default implementation.
|
||||
*/
|
||||
getScreenSizePixels?: boolean;
|
||||
/**
|
||||
* Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t".
|
||||
* Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t".
|
||||
* Has a default implementation.
|
||||
*/
|
||||
getCellSizePixels?: boolean;
|
||||
/**
|
||||
* Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t".
|
||||
* Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t".
|
||||
* Has a default implementation.
|
||||
*/
|
||||
getWinSizeChars?: boolean;
|
||||
/**
|
||||
* Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t".
|
||||
* Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t".
|
||||
* No default implementation.
|
||||
*/
|
||||
getScreenSizeChars?: boolean;
|
||||
/**
|
||||
* Ps=20 Report xterm window's icon label. Result is "OSC L label ST".
|
||||
* Ps=20 Report xterm window's icon label. Result is "OSC L label ST".
|
||||
* No default implementation.
|
||||
*/
|
||||
getIconTitle?: boolean;
|
||||
/**
|
||||
* Ps=21 Report xterm window's title. Result is "OSC l label ST".
|
||||
* Ps=21 Report xterm window's title. Result is "OSC l label ST".
|
||||
* No default implementation.
|
||||
*/
|
||||
getWinTitle?: boolean;
|
||||
/**
|
||||
* Ps=22 ; 0 Save xterm icon and window title on stack.
|
||||
* Ps=22 ; 1 Save xterm icon title on stack.
|
||||
* Ps=22 ; 2 Save xterm window title on stack.
|
||||
* Ps=22 ; 2 Save xterm window title on stack.
|
||||
* All variants have a default implementation.
|
||||
*/
|
||||
pushTitle?: boolean;
|
||||
/**
|
||||
* Ps=23 ; 0 Restore xterm icon and window title from stack.
|
||||
* Ps=23 ; 1 Restore xterm icon title from stack.
|
||||
* Ps=23 ; 2 Restore xterm window title from stack.
|
||||
* Ps=23 ; 2 Restore xterm window title from stack.
|
||||
* All variants have a default implementation.
|
||||
*/
|
||||
popTitle?: boolean;
|
||||
/**
|
||||
* Ps>=24 Resize to Ps lines (DECSLPP).
|
||||
* Ps>=24 Resize to Ps lines (DECSLPP).
|
||||
* DECSLPP is not implemented. This settings is also used to
|
||||
* enable / disable DECCOLM (earlier variant of DECSLPP).
|
||||
*/
|
||||
@ -722,6 +722,8 @@ declare module 'xterm' {
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
|
||||
* be matched and handled.
|
||||
* @deprecated The link matcher API is now deprecated in favor of the link
|
||||
* provider API, see `registerLinkProvider`.
|
||||
* @param regex The regular expression to search for, specifically this
|
||||
* searches the textContent of the rows. You will want to use \s to match a
|
||||
* space ' ' character for example.
|
||||
@ -733,10 +735,20 @@ declare module 'xterm' {
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Deregisters a link matcher if it has been registered.
|
||||
* @deprecated The link matcher API is now deprecated in favor of the link
|
||||
* provider API, see `registerLinkProvider`.
|
||||
* @param matcherId The link matcher's ID (returned after register)
|
||||
*/
|
||||
deregisterLinkMatcher(matcherId: number): void;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a link provider, allowing a custom parser to
|
||||
* be used to match and handle links. Multiple link providers can be used,
|
||||
* they will be asked in the order in which they are registered.
|
||||
* @param linkProvider The link provider to use to detect links.
|
||||
*/
|
||||
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
|
||||
* characters to be rendered as a single unit. This is useful in particular
|
||||
@ -1073,6 +1085,85 @@ declare module 'xterm' {
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom link provider.
|
||||
*/
|
||||
interface ILinkProvider {
|
||||
/**
|
||||
* Provides a link a buffer position
|
||||
* @param position The position of the buffer that is currently active.
|
||||
* @param callback The callback to be fired with the resulting link or
|
||||
* `undefined` when ready.
|
||||
*/
|
||||
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A link within the terminal.
|
||||
*/
|
||||
interface ILink {
|
||||
/**
|
||||
* The buffer range of the link.
|
||||
*/
|
||||
range: IBufferRange;
|
||||
|
||||
/**
|
||||
* The text of the link.
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Calls when the link is activated.
|
||||
* @param event The mouse event triggering the callback.
|
||||
* @param text The text of the link.
|
||||
*/
|
||||
activate(event: MouseEvent, text: string): void;
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers the link.
|
||||
* @param event The mouse event triggering the callback.
|
||||
* @param text The text of the link.
|
||||
*/
|
||||
hover?(event: MouseEvent, text: string): void;
|
||||
|
||||
/**
|
||||
* Called when the mouse leaves the link.
|
||||
* @param event The mouse event triggering the callback.
|
||||
* @param text The text of the link.
|
||||
*/
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A range within a buffer.
|
||||
*/
|
||||
interface IBufferRange {
|
||||
/**
|
||||
* The start position of the range.
|
||||
*/
|
||||
start: IBufferCellPosition;
|
||||
|
||||
/**
|
||||
* The end position of the range.
|
||||
*/
|
||||
end: IBufferCellPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* A position within a buffer.
|
||||
*/
|
||||
interface IBufferCellPosition {
|
||||
/**
|
||||
* The x position within the buffer.
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* The y position within the buffer.
|
||||
*/
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a terminal buffer.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user