Skip to content

Move several components to sub-projects #2309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* @license MIT
*/

import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types';
import { ILinkifierAccessor } from '../../../../src/Types';
import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from '../atlas/CharAtlasUtils';
import { IColorSet } from 'browser/Types';
import { IColorSet, ILinkifierEvent } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';

export class LinkRenderLayer extends BaseRenderLayer {
Expand Down Expand Up @@ -45,9 +45,9 @@ export class LinkRenderLayer extends BaseRenderLayer {
private _onLinkHover(e: ILinkifierEvent): void {
if (e.fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
} else if (is256Color(e.fg)) {
} else if (e.fg !== undefined && is256Color(e.fg)) {
// 256 color support
this._ctx.fillStyle = this._colors.ansi[e.fg].css;
this._ctx.fillStyle = this._colors.ansi[e.fg!].css;
} else {
this._ctx.fillStyle = this._colors.foreground.css;
}
Expand Down
54 changes: 24 additions & 30 deletions src/MouseZoneManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
* @license MIT
*/

import { ITerminal, IMouseZoneManager, IMouseZone } from './Types';
import { Disposable } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IMouseService } from 'browser/services/Services';
import { IMouseService, ISelectionService } from 'browser/services/Services';
import { IMouseZoneManager, IMouseZone } from 'browser/Types';
import { IBufferService } from 'common/services/Services';

const HOVER_DURATION = 500;

Expand All @@ -32,12 +33,15 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _initialSelectionLength: number;

constructor(
private _terminal: ITerminal,
private _mouseService: IMouseService
private readonly _element: HTMLElement,
private readonly _screenElement: HTMLElement,
private readonly _bufferService: IBufferService,
private readonly _mouseService: IMouseService,
private readonly _selectionService: ISelectionService
) {
super();

this.register(addDisposableDomListener(this._terminal.element, 'mousedown', e => this._onMouseDown(e)));
this.register(addDisposableDomListener(this._element, 'mousedown', e => this._onMouseDown(e)));

// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
Expand Down Expand Up @@ -66,7 +70,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// Clear all if start/end weren't set
if (!end) {
start = 0;
end = this._terminal.rows - 1;
end = this._bufferService.rows - 1;
}

// Iterate through zones and clear them out if they're within the range
Expand All @@ -92,18 +96,18 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _activate(): void {
if (!this._areZonesActive) {
this._areZonesActive = true;
this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.addEventListener('click', this._clickListener);
this._element.addEventListener('mousemove', this._mouseMoveListener);
this._element.addEventListener('mouseleave', this._mouseLeaveListener);
this._element.addEventListener('click', this._clickListener);
}
}

private _deactivate(): void {
if (this._areZonesActive) {
this._areZonesActive = false;
this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.removeEventListener('click', this._clickListener);
this._element.removeEventListener('mousemove', this._mouseMoveListener);
this._element.removeEventListener('mouseleave', this._mouseLeaveListener);
this._element.removeEventListener('click', this._clickListener);
}
}

Expand Down Expand Up @@ -161,7 +165,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _onMouseDown(e: MouseEvent): void {
// Store current terminal selection length, to check if we're performing
// a selection operation
this._initialSelectionLength = this._terminal.getSelection().length;
this._initialSelectionLength = this._getSelectionLength();

// Ignore the event if there are no zones active
if (!this._areZonesActive) {
Expand Down Expand Up @@ -195,7 +199,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// Find the active zone and click it if found and no selection was
// being performed
const zone = this._findZoneEventAt(e);
const currentSelectionLength = this._terminal.getSelection().length;
const currentSelectionLength = this._getSelectionLength();

if (zone && currentSelectionLength === this._initialSelectionLength) {
zone.clickCallback(e);
Expand All @@ -204,8 +208,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
}

private _getSelectionLength(): number {
const selectionText = this._selectionService.selectionText;
return selectionText ? selectionText.length : 0;
}

private _findZoneEventAt(e: MouseEvent): IMouseZone {
const coords = this._mouseService.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows);
const coords = this._mouseService.getCoords(e, this._screenElement, this._bufferService.cols, this._bufferService.rows);
if (!coords) {
return null;
}
Expand All @@ -230,18 +239,3 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
return null;
}
}

export class MouseZone implements IMouseZone {
constructor(
public x1: number,
public y1: number,
public x2: number,
public y2: number,
public clickCallback: (e: MouseEvent) => any,
public hoverCallback: (e: MouseEvent) => any,
public tooltipCallback: (e: MouseEvent) => any,
public leaveCallback: () => void,
public willLinkActivate: (e: MouseEvent) => boolean
) {
}
}
132 changes: 125 additions & 7 deletions src/Terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@
*/

import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test';
import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { wcwidth } from 'common/CharWidth';
import { IBufferService } from 'common/services/Services';
import { Linkifier } from 'browser/Linkifier';
import { MockLogService } from 'common/TestUtils.test';
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';

const INIT_COLS = 80;
const INIT_ROWS = 24;

class TestTerminal extends Terminal {
public keyDown(ev: any): boolean { return this._keyDown(ev); }
public keyPress(ev: any): boolean { return this._keyPress(ev); }
}

describe('Terminal', () => {
let term: TestTerminal;
const termOptions = {
Expand Down Expand Up @@ -1024,4 +1022,124 @@ describe('Terminal', () => {
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
});
});

describe('Linkifier unicode handling', () => {
let terminal: TestTerminal;
let linkifier: TestLinkifier;
let mouseZoneManager: TestMouseZoneManager;

// other than the tests above unicode testing needs the full terminal instance
// to get the special handling of fullwidth, surrogate and combining chars in the input handler
beforeEach(() => {
terminal = new TestTerminal({ cols: 10, rows: 5 });
linkifier = new TestLinkifier((terminal as any)._bufferService);
mouseZoneManager = new TestMouseZoneManager();
linkifier.attachToDom({} as any, mouseZoneManager);
});

function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
terminal.writeSync(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
linkifier.linkifyRows();
// Allow linkify to happen
setTimeout(() => {
assert.equal(mouseZoneManager.zones.length, links.length);
links.forEach((l, i) => {
assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1);
assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1);
assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1);
assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1);
});
done();
}, 0);
}

describe('unicode before the match', () => {
it('combining - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
});
it('combining - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
});
it('surrogate - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
});
it('surrogate - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
});
it('combining surrogate - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 4, x2: 7, y1: 0, y2: 0}], done);
});
it('combining surrogate - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
});
it('fullwidth - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
});
it('fullwidth - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('12 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
});
it('combining fullwidth - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
});
it('combining fullwidth - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{x1: 8, x2: 1, y1: 0, y2: 1}], done);
});
});
describe('unicode within the match', () => {
it('combining - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
});
it('combining - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
});
it('surrogate - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
});
it('surrogate - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done);
});
it('combining surrogate - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 5, x2: 8, y1: 0, y2: 0}], done);
});
it('combining surrogate - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{x1: 9, x2: 2, y1: 0, y2: 1}], done);
});
it('fullwidth - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('test a1b', /a1b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
});
it('fullwidth - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('testtest a1b', /a1b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
});
it('combining fullwidth - match within one line', function(done: () => void): void {
assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{x1: 5, x2: 9, y1: 0, y2: 0}], done);
});
it('combining fullwidth - match over two lines', function(done: () => void): void {
assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{x1: 9, x2: 3, y1: 0, y2: 1}], done);
});
});
});
});

class TestLinkifier extends Linkifier {
constructor(bufferService: IBufferService) {
super(bufferService, new MockLogService());
Linkifier._timeBeforeLatency = 0;
}

public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; }
public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); }
}

class TestMouseZoneManager implements IMouseZoneManager {
dispose(): void {
}
public clears: number = 0;
public zones: IMouseZone[] = [];
add(zone: IMouseZone): void {
this.zones.push(zone);
}
clearAll(): void {
this.clears++;
}
}
21 changes: 10 additions & 11 deletions src/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
* http://linux.die.net/man/7/urxvt
*/

import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, IMouseZoneManager } from './Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types';
import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { Viewport } from './Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard';
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from 'browser/Clipboard';
import { C0 } from 'common/data/EscapeSequences';
import { InputHandler } from './InputHandler';
import { Renderer } from './renderer/Renderer';
import { Linkifier } from './Linkifier';
import { Linkifier } from 'browser/Linkifier';
import { SelectionService } from './browser/services/SelectionService';
import * as Browser from 'common/Platform';
import { addDisposableDomListener } from 'browser/Lifecycle';
Expand Down Expand Up @@ -59,6 +59,7 @@ import { MouseService } from 'browser/services/MouseService';
import { IParams } from 'common/parser/Types';
import { CoreService } from 'common/services/CoreService';
import { LogService } from 'common/services/LogService';
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions } from 'browser/Types';

// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
Expand Down Expand Up @@ -304,9 +305,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._inputHandler.onLineFeed(() => this._onLineFeed.fire());
this.register(this._inputHandler);

this._selectionService = this._selectionService || null;
this.linkifier = this.linkifier || new Linkifier(this, this._logService);
this._mouseZoneManager = this._mouseZoneManager || null;
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);

if (this.options.windowsMode) {
this._windowsMode = applyWindowsMode(this);
Expand Down Expand Up @@ -600,11 +599,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._soundService = new SoundService(this.optionsService);
this._mouseService = new MouseService(this._renderService, this._charSizeService);

this._mouseZoneManager = new MouseZoneManager(this, this._mouseService);
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this._mouseZoneManager);

this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService);
this.viewport.onThemeChange(this._colorManager.colors);
this.register(this.viewport);
Expand Down Expand Up @@ -637,6 +631,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}));
this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService.refresh()));

this._mouseZoneManager = new MouseZoneManager(this.element, this.screenElement, this._bufferService, this._mouseService, this._selectionService);
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);

// apply mouse event classes set by escape codes before terminal was attached
this.element.classList.toggle('enable-mouse-events', this.mouseEvents);
if (this.mouseEvents) {
Expand Down
6 changes: 4 additions & 2 deletions src/TestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
*/

import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
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 { Terminal } from './Terminal';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorManager, IColorSet } from 'browser/Types';
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { IParams } from 'common/parser/Types';
Expand All @@ -23,6 +23,8 @@ export class TestTerminal extends Terminal {
this.writeBuffer.push(data);
this._innerWrite();
}
keyDown(ev: any): boolean { return this._keyDown(ev); }
keyPress(ev: any): boolean { return this._keyPress(ev); }
}

export class MockTerminal implements ITerminal {
Expand Down
Loading