Merge branch 'master' into unused_func
This commit is contained in:
@@ -11,7 +11,6 @@ import { addDisposableListener } from './utils/Dom';
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-';
|
||||
|
||||
enum BoundaryPosition {
|
||||
Top,
|
||||
@@ -21,7 +20,7 @@ enum BoundaryPosition {
|
||||
export class AccessibilityManager implements IDisposable {
|
||||
private _accessibilityTreeRoot: HTMLElement;
|
||||
private _rowContainer: HTMLElement;
|
||||
private _rowElements: HTMLElement[] = [];
|
||||
private _rowElements: HTMLElement[];
|
||||
private _liveRegion: HTMLElement;
|
||||
private _liveRegionLineCount: number = 0;
|
||||
|
||||
@@ -49,6 +48,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowElements = [];
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
this._rowContainer.appendChild(this._rowElements[i]);
|
||||
@@ -93,14 +93,10 @@ export class AccessibilityManager implements IDisposable {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._terminal.element.removeChild(this._accessibilityTreeRoot);
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
this._disposables = null;
|
||||
this._accessibilityTreeRoot = null;
|
||||
this._rowContainer = null;
|
||||
this._liveRegion = null;
|
||||
this._rowContainer = null;
|
||||
this._rowElements = null;
|
||||
this._disposables.length = 0;
|
||||
this._terminal.element.removeChild(this._accessibilityTreeRoot);
|
||||
this._rowElements.length = 0;
|
||||
}
|
||||
|
||||
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
|
||||
@@ -125,10 +121,10 @@ export class AccessibilityManager implements IDisposable {
|
||||
let bottomBoundaryElement: HTMLElement;
|
||||
if (position === BoundaryPosition.Top) {
|
||||
topBoundaryElement = boundaryElement;
|
||||
bottomBoundaryElement = this._rowElements.pop();
|
||||
bottomBoundaryElement = <HTMLElement>this._rowElements.pop();
|
||||
this._rowContainer.removeChild(bottomBoundaryElement);
|
||||
} else {
|
||||
topBoundaryElement = this._rowElements.shift();
|
||||
topBoundaryElement = <HTMLElement>this._rowElements.shift();
|
||||
bottomBoundaryElement = boundaryElement;
|
||||
this._rowContainer.removeChild(topBoundaryElement);
|
||||
}
|
||||
@@ -174,7 +170,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
}
|
||||
// Shrink rows as required
|
||||
while (this._rowElements.length > rows) {
|
||||
this._rowContainer.removeChild(this._rowElements.pop());
|
||||
this._rowContainer.removeChild(<HTMLElement>this._rowElements.pop());
|
||||
}
|
||||
|
||||
// Add bottom boundary listener
|
||||
@@ -218,7 +214,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
|
||||
if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
|
||||
setTimeout(() => {
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
}, 0);
|
||||
@@ -265,7 +261,6 @@ export class AccessibilityManager implements IDisposable {
|
||||
if (!this._terminal.renderer.dimensions.actualCellHeight) {
|
||||
return;
|
||||
}
|
||||
const buffer: IBuffer = this._terminal.buffer;
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._refreshRowDimensions(this._rowElements[i]);
|
||||
}
|
||||
|
||||
+61
-61
@@ -20,43 +20,43 @@ export class CompositionHelper {
|
||||
* Whether input composition is currently happening, eg. via a mobile keyboard, speech input or
|
||||
* IME. This variable determines whether the compositionText should be displayed on the UI.
|
||||
*/
|
||||
private isComposing: boolean;
|
||||
private _isComposing: boolean;
|
||||
|
||||
/**
|
||||
* The position within the input textarea's value of the current composition.
|
||||
*/
|
||||
private compositionPosition: IPosition;
|
||||
private _compositionPosition: IPosition;
|
||||
|
||||
/**
|
||||
* Whether a composition is in the process of being sent, setting this to false will cancel any
|
||||
* in-progress composition.
|
||||
*/
|
||||
private isSendingComposition: boolean;
|
||||
private _isSendingComposition: boolean;
|
||||
|
||||
/**
|
||||
* Creates a new CompositionHelper.
|
||||
* @param textarea The textarea that xterm uses for input.
|
||||
* @param compositionView The element to display the in-progress composition in.
|
||||
* @param terminal The Terminal to forward the finished composition to.
|
||||
* @param _textarea The textarea that xterm uses for input.
|
||||
* @param _compositionView The element to display the in-progress composition in.
|
||||
* @param _terminal The Terminal to forward the finished composition to.
|
||||
*/
|
||||
constructor(
|
||||
private textarea: HTMLTextAreaElement,
|
||||
private compositionView: HTMLElement,
|
||||
private terminal: ITerminal
|
||||
private _textarea: HTMLTextAreaElement,
|
||||
private _compositionView: HTMLElement,
|
||||
private _terminal: ITerminal
|
||||
) {
|
||||
this.isComposing = false;
|
||||
this.isSendingComposition = false;
|
||||
this.compositionPosition = { start: null, end: null };
|
||||
this._isComposing = false;
|
||||
this._isSendingComposition = false;
|
||||
this._compositionPosition = { start: null, end: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionstart event, activating the composition view.
|
||||
*/
|
||||
public compositionstart(): void {
|
||||
this.isComposing = true;
|
||||
this.compositionPosition.start = this.textarea.value.length;
|
||||
this.compositionView.textContent = '';
|
||||
this.compositionView.classList.add('active');
|
||||
this._isComposing = true;
|
||||
this._compositionPosition.start = this._textarea.value.length;
|
||||
this._compositionView.textContent = '';
|
||||
this._compositionView.classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,10 +64,10 @@ export class CompositionHelper {
|
||||
* @param {CompositionEvent} ev The event.
|
||||
*/
|
||||
public compositionupdate(ev: CompositionEvent): void {
|
||||
this.compositionView.textContent = ev.data;
|
||||
this._compositionView.textContent = ev.data;
|
||||
this.updateCompositionElements();
|
||||
setTimeout(() => {
|
||||
this.compositionPosition.end = this.textarea.value.length;
|
||||
this._compositionPosition.end = this._textarea.value.length;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class CompositionHelper {
|
||||
* the handler.
|
||||
*/
|
||||
public compositionend(): void {
|
||||
this.finalizeComposition(true);
|
||||
this._finalizeComposition(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +85,7 @@ export class CompositionHelper {
|
||||
* @return Whether the Terminal should continue processing the keydown event.
|
||||
*/
|
||||
public keydown(ev: KeyboardEvent): boolean {
|
||||
if (this.isComposing || this.isSendingComposition) {
|
||||
if (this._isComposing || this._isSendingComposition) {
|
||||
if (ev.keyCode === 229) {
|
||||
// Continue composing if the keyCode is the "composition character"
|
||||
return false;
|
||||
@@ -95,14 +95,14 @@ export class CompositionHelper {
|
||||
} else {
|
||||
// Finish composition immediately. This is mainly here for the case where enter is
|
||||
// pressed and the handler needs to be triggered before the command is executed.
|
||||
this.finalizeComposition(false);
|
||||
this._finalizeComposition(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.keyCode === 229) {
|
||||
// If the "composition character" is used but gets to this point it means a non-composition
|
||||
// character (eg. numbers and punctuation) was pressed when the IME was active.
|
||||
this.handleAnyTextareaChanges();
|
||||
this._handleAnyTextareaChanges();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,22 +117,22 @@ export class CompositionHelper {
|
||||
* compositionend event is triggered, such as enter, so that the composition is send before
|
||||
* the command is executed.
|
||||
*/
|
||||
private finalizeComposition(waitForPropogation: boolean): void {
|
||||
this.compositionView.classList.remove('active');
|
||||
this.isComposing = false;
|
||||
this.clearTextareaPosition();
|
||||
private _finalizeComposition(waitForPropogation: boolean): void {
|
||||
this._compositionView.classList.remove('active');
|
||||
this._isComposing = false;
|
||||
this._clearTextareaPosition();
|
||||
|
||||
if (!waitForPropogation) {
|
||||
// Cancel any delayed composition send requests and send the input immediately.
|
||||
this.isSendingComposition = false;
|
||||
const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
|
||||
this.terminal.handler(input);
|
||||
this._isSendingComposition = false;
|
||||
const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);
|
||||
this._terminal.handler(input);
|
||||
} else {
|
||||
// Make a deep copy of the composition position here as a new compositionstart event may
|
||||
// fire before the setTimeout executes.
|
||||
const currentCompositionPosition = {
|
||||
start: this.compositionPosition.start,
|
||||
end: this.compositionPosition.end,
|
||||
start: this._compositionPosition.start,
|
||||
end: this._compositionPosition.end,
|
||||
};
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
@@ -143,22 +143,22 @@ export class CompositionHelper {
|
||||
// - The last compositionupdate event's data property does not always accurately describe
|
||||
// the character, a counter example being Korean where an ending consonsant can move to
|
||||
// the following character if the following input is a vowel.
|
||||
this.isSendingComposition = true;
|
||||
this._isSendingComposition = true;
|
||||
setTimeout(() => {
|
||||
// Ensure that the input has not already been sent
|
||||
if (this.isSendingComposition) {
|
||||
this.isSendingComposition = false;
|
||||
if (this._isSendingComposition) {
|
||||
this._isSendingComposition = false;
|
||||
let input;
|
||||
if (this.isComposing) {
|
||||
if (this._isComposing) {
|
||||
// Use the end position to get the string if a new composition has started.
|
||||
input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
||||
} else {
|
||||
// Don't use the end position here in order to pick up any characters after the
|
||||
// composition has finished, for example when typing a non-composition character
|
||||
// (eg. 2) after a composition character.
|
||||
input = this.textarea.value.substring(currentCompositionPosition.start);
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start);
|
||||
}
|
||||
this.terminal.handler(input);
|
||||
this._terminal.handler(input);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
@@ -170,15 +170,15 @@ export class CompositionHelper {
|
||||
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
||||
* IME is active.
|
||||
*/
|
||||
private handleAnyTextareaChanges(): void {
|
||||
const oldValue = this.textarea.value;
|
||||
private _handleAnyTextareaChanges(): void {
|
||||
const oldValue = this._textarea.value;
|
||||
setTimeout(() => {
|
||||
// Ignore if a composition has started since the timeout
|
||||
if (!this.isComposing) {
|
||||
const newValue = this.textarea.value;
|
||||
if (!this._isComposing) {
|
||||
const newValue = this._textarea.value;
|
||||
const diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
this.terminal.handler(diff);
|
||||
this._terminal.handler(diff);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
@@ -191,27 +191,27 @@ export class CompositionHelper {
|
||||
* necessary as the IME events across browsers are not consistently triggered.
|
||||
*/
|
||||
public updateCompositionElements(dontRecurse?: boolean): void {
|
||||
if (!this.isComposing) {
|
||||
if (!this._isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.terminal.buffer.isCursorInViewport) {
|
||||
const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight);
|
||||
const cursorTop = this.terminal.buffer.y * cellHeight;
|
||||
const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width;
|
||||
if (this._terminal.buffer.isCursorInViewport) {
|
||||
const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight);
|
||||
const cursorTop = this._terminal.buffer.y * cellHeight;
|
||||
const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width;
|
||||
|
||||
this.compositionView.style.left = cursorLeft + 'px';
|
||||
this.compositionView.style.top = cursorTop + 'px';
|
||||
this.compositionView.style.height = cellHeight + 'px';
|
||||
this.compositionView.style.lineHeight = cellHeight + 'px';
|
||||
this._compositionView.style.left = cursorLeft + 'px';
|
||||
this._compositionView.style.top = cursorTop + 'px';
|
||||
this._compositionView.style.height = cellHeight + 'px';
|
||||
this._compositionView.style.lineHeight = cellHeight + 'px';
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
const compositionViewBounds = this.compositionView.getBoundingClientRect();
|
||||
this.textarea.style.left = cursorLeft + 'px';
|
||||
this.textarea.style.top = cursorTop + 'px';
|
||||
this.textarea.style.width = compositionViewBounds.width + 'px';
|
||||
this.textarea.style.height = compositionViewBounds.height + 'px';
|
||||
this.textarea.style.lineHeight = compositionViewBounds.height + 'px';
|
||||
const compositionViewBounds = this._compositionView.getBoundingClientRect();
|
||||
this._textarea.style.left = cursorLeft + 'px';
|
||||
this._textarea.style.top = cursorTop + 'px';
|
||||
this._textarea.style.width = compositionViewBounds.width + 'px';
|
||||
this._textarea.style.height = compositionViewBounds.height + 'px';
|
||||
this._textarea.style.lineHeight = compositionViewBounds.height + 'px';
|
||||
}
|
||||
|
||||
if (!dontRecurse) {
|
||||
@@ -223,8 +223,8 @@ export class CompositionHelper {
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
* @private
|
||||
*/
|
||||
private clearTextareaPosition(): void {
|
||||
this.textarea.style.left = '';
|
||||
this.textarea.style.top = '';
|
||||
private _clearTextareaPosition(): void {
|
||||
this._textarea.style.left = '';
|
||||
this._textarea.style.top = '';
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CharData, IInputHandler, IInputHandlingTerminal, ITerminal } from './Types';
|
||||
import { CharData, IInputHandler, IInputHandlingTerminal } from './Types';
|
||||
import { C0 } from './EscapeSequences';
|
||||
import { DEFAULT_CHARSET } from './Charsets';
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
|
||||
|
||||
+4
-21
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { IMouseZoneManager, IMouseZone } from './input/Types';
|
||||
import { ILinkMatcher, LineData, ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Types';
|
||||
import { ILinkMatcher, LineData, IBufferAccessor, IElementAccessor } from './Types';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { MockBuffer } from './utils/TestUtils.test';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
@@ -59,17 +59,6 @@ describe('Linkifier', () => {
|
||||
terminal.buffer.lines.push(stringToRow(text));
|
||||
}
|
||||
|
||||
function assertLinkifiesEntireRow(uri: string, done: MochaDone): void {
|
||||
addRow(uri);
|
||||
linkifier.linkifyRows();
|
||||
setTimeout(() => {
|
||||
assert.equal(mouseZoneManager.zones[0].x1, 1);
|
||||
assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1);
|
||||
assert.equal(mouseZoneManager.zones[0].y, terminal.buffer.lines.length);
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
@@ -101,12 +90,6 @@ describe('Linkifier', () => {
|
||||
linkifier.attachToDom(mouseZoneManager);
|
||||
});
|
||||
|
||||
describe('http links', () => {
|
||||
it('should allow ~ character in URI path', (done) => {
|
||||
assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('link matcher', () => {
|
||||
it('should match a single link', done => {
|
||||
assertLinkifiesRow('foo', /foo/, [{x: 0, length: 3}], done);
|
||||
@@ -200,19 +183,19 @@ describe('Linkifier', () => {
|
||||
it('should order the list from highest priority to lowest #1', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, 0, bId]);
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]);
|
||||
});
|
||||
|
||||
it('should order the list from highest priority to lowest #2', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, 0, aId]);
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, aId]);
|
||||
});
|
||||
|
||||
it('should order items of equal priority in the order they are added', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [0, aId, bId]);
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+6
-53
@@ -4,34 +4,10 @@
|
||||
*/
|
||||
|
||||
import { IMouseZoneManager } from './input/Types';
|
||||
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Types';
|
||||
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, IBufferAccessor, ILinkifier, IElementAccessor } from './Types';
|
||||
import { MouseZone } from './input/MouseZoneManager';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
const domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
const tldClause = '([a-z\\.]{2,6})';
|
||||
const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
const start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
const end = ')($|' + negatedPathCharacterSet + ')';
|
||||
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
|
||||
/**
|
||||
* The ID of the built in http(s) link matcher.
|
||||
*/
|
||||
const HYPERTEXT_LINK_MATCHER_ID = 0;
|
||||
|
||||
/**
|
||||
* The Linkifier applies links to rows shortly after they have been refreshed.
|
||||
*/
|
||||
@@ -47,7 +23,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
|
||||
private _mouseZoneManager: IMouseZoneManager;
|
||||
private _rowsTimeoutId: number;
|
||||
private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
|
||||
private _nextLinkMatcherId = 0;
|
||||
private _rowsToLinkify: {start: number, end: number};
|
||||
|
||||
constructor(
|
||||
@@ -58,7 +34,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,23 +86,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
this._rowsToLinkify.end = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a handler for hypertext links, overriding default <a> behavior for
|
||||
* tandard http(s) links.
|
||||
* @param handler The handler to use, this can be cleared with null.
|
||||
*/
|
||||
public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
|
||||
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a validation callback for hypertext links.
|
||||
* @param callback The callback to use, this can be cleared with null.
|
||||
*/
|
||||
public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
|
||||
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a link matcher, allowing custom link patterns to be matched and
|
||||
* handled.
|
||||
@@ -139,7 +97,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* @return The ID of the new matcher, this can be used to deregister.
|
||||
*/
|
||||
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {
|
||||
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
|
||||
if (!handler) {
|
||||
throw new Error('handler must be defined');
|
||||
}
|
||||
const matcher: ILinkMatcher = {
|
||||
@@ -185,8 +143,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* @return Whether a link matcher was found and deregistered.
|
||||
*/
|
||||
public deregisterLinkMatcher(matcherId: number): boolean {
|
||||
// ID 0 is the hypertext link matcher which cannot be deregistered
|
||||
for (let i = 1; i < this._linkMatchers.length; i++) {
|
||||
for (let i = 0; i < this._linkMatchers.length; i++) {
|
||||
if (this._linkMatchers[i].id === matcherId) {
|
||||
this._linkMatchers.splice(i, 1);
|
||||
return true;
|
||||
@@ -220,10 +177,6 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
* @return The link element(s) that were added.
|
||||
*/
|
||||
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
|
||||
// Iterate over nodes as we want to consider text nodes
|
||||
let result = [];
|
||||
const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
|
||||
|
||||
// Find the first match
|
||||
let match = text.match(matcher.regex);
|
||||
if (!match || match.length === 0) {
|
||||
@@ -277,7 +230,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
},
|
||||
e => {
|
||||
this.emit(LinkHoverEventTypes.HOVER, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
this._terminal.element.style.cursor = 'pointer';
|
||||
this._terminal.element.classList.add('xterm-cursor-pointer');
|
||||
},
|
||||
e => {
|
||||
this.emit(LinkHoverEventTypes.TOOLTIP, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
@@ -287,7 +240,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
},
|
||||
() => {
|
||||
this.emit(LinkHoverEventTypes.LEAVE, <ILinkHoverEvent>{ x, y, length: uri.length});
|
||||
this._terminal.element.style.cursor = '';
|
||||
this._terminal.element.classList.remove('xterm-cursor-pointer');
|
||||
if (matcher.hoverLeaveCallback) {
|
||||
matcher.hoverLeaveCallback();
|
||||
}
|
||||
|
||||
+2
-3
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { C0 } from './EscapeSequences';
|
||||
import { IInputHandler } from './Types';
|
||||
import { IInputHandler, IInputHandlingTerminal } from './Types';
|
||||
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
|
||||
|
||||
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
|
||||
@@ -185,7 +185,6 @@ export class Parser {
|
||||
*/
|
||||
public parse(data: string): ParserState {
|
||||
const l = data.length;
|
||||
let j;
|
||||
let cs;
|
||||
let ch;
|
||||
let code;
|
||||
@@ -346,7 +345,7 @@ export class Parser {
|
||||
|
||||
// ESC H Tab Set (HTS is 0x88).
|
||||
case 'H':
|
||||
this._terminal.tabSet();
|
||||
(<IInputHandlingTerminal>this._terminal).tabSet();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { SelectionManager } from './SelectionManager';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import { BufferSet } from './BufferSet';
|
||||
import { LineData, CharData, ITerminal, ICircularList, IBuffer } from './Types';
|
||||
import { LineData, CharData, ITerminal, IBuffer } from './Types';
|
||||
import { MockTerminal } from './utils/TestUtils.test';
|
||||
|
||||
class TestMockTerminal extends MockTerminal {
|
||||
@@ -37,19 +35,11 @@ class TestSelectionManager extends SelectionManager {
|
||||
}
|
||||
|
||||
describe('SelectionManager', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let terminal: ITerminal;
|
||||
let buffer: IBuffer;
|
||||
let rowContainer: HTMLElement;
|
||||
let selectionManager: TestSelectionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
terminal = new TestMockTerminal();
|
||||
terminal.cols = 80;
|
||||
terminal.rows = 2;
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData, XtermListener } from './Types';
|
||||
import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener } from './Types';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import * as Browser from './shared/utils/Browser';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { SelectionModel } from './SelectionModel';
|
||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||
@@ -634,6 +633,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* @param coords The coordinates to get the word at.
|
||||
*/
|
||||
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition {
|
||||
// Ensure coords are within viewport (eg. not within scroll bar)
|
||||
if (coords[0] >= this._terminal.cols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bufferLine = this._buffer.lines.get(coords[1]);
|
||||
if (!bufferLine) {
|
||||
return null;
|
||||
|
||||
@@ -18,9 +18,6 @@ class TestSelectionModel extends SelectionModel {
|
||||
}
|
||||
|
||||
describe('SelectionManager', () => {
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let terminal: ITerminal;
|
||||
let model: TestSelectionModel;
|
||||
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ export class SoundManager implements ISoundManager {
|
||||
if (this._audioContext) {
|
||||
const bellAudioSource = this._audioContext.createBufferSource();
|
||||
const context = this._audioContext;
|
||||
this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||
this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||
bellAudioSource.buffer = buffer;
|
||||
bellAudioSource.connect(context.destination);
|
||||
bellAudioSource.start(0);
|
||||
@@ -38,7 +38,7 @@ export class SoundManager implements ISoundManager {
|
||||
}
|
||||
}
|
||||
|
||||
private base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
const binaryString = window.atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
@@ -50,7 +50,7 @@ export class SoundManager implements ISoundManager {
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
private removeMimeType(dataURI: string): string {
|
||||
private _removeMimeType(dataURI: string): string {
|
||||
// Split the input to get the mime-type and the data itself
|
||||
const splitUri = dataURI.split(',');
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ if (os.platform() !== 'win32') {
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(fromPty);
|
||||
xterm.innerWrite();
|
||||
xterm._innerWrite();
|
||||
|
||||
let fromEmulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
|
||||
@@ -29,11 +29,11 @@ describe('term.js addons', () => {
|
||||
term.refresh = () => {};
|
||||
(<any>term).renderer = new MockRenderer();
|
||||
term.viewport = new MockViewport();
|
||||
(<any>term).compositionHelper = new MockCompositionHelper();
|
||||
(<any>term)._compositionHelper = new MockCompositionHelper();
|
||||
// Force synchronous writes
|
||||
term.write = (data) => {
|
||||
term.writeBuffer.push(data);
|
||||
(<any>term).innerWrite();
|
||||
(<any>term)._innerWrite();
|
||||
};
|
||||
(<any>term).element = {
|
||||
classList: {
|
||||
|
||||
+157
-198
@@ -21,7 +21,7 @@
|
||||
* http://linux.die.net/man/7/urxvt
|
||||
*/
|
||||
|
||||
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
|
||||
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types';
|
||||
import { IMouseZoneManager } from './input/Types';
|
||||
import { IRenderer } from './renderer/Types';
|
||||
import { BufferSet } from './BufferSet';
|
||||
@@ -30,7 +30,6 @@ import { CompositionHelper } from './CompositionHelper';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { Viewport } from './Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { C0 } from './EscapeSequences';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { Parser } from './Parser';
|
||||
@@ -41,7 +40,6 @@ import { CharMeasure } from './utils/CharMeasure';
|
||||
import * as Browser from './shared/utils/Browser';
|
||||
import * as Strings from './Strings';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import { CHARSETS } from './Charsets';
|
||||
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
|
||||
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
||||
import { MouseZoneManager } from './input/MouseZoneManager';
|
||||
@@ -120,7 +118,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
allowTransparency: false,
|
||||
tabStopWidth: 8,
|
||||
theme: null,
|
||||
rightClickSelectsWord: Browser.isMac
|
||||
rightClickSelectsWord: Browser.isMac,
|
||||
// programFeatures: false,
|
||||
// focusKeys: false,
|
||||
};
|
||||
@@ -133,30 +131,27 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
/**
|
||||
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
||||
*/
|
||||
private parent: HTMLElement;
|
||||
private context: Window;
|
||||
private document: Document;
|
||||
private body: HTMLBodyElement;
|
||||
private viewportScrollArea: HTMLElement;
|
||||
private viewportElement: HTMLElement;
|
||||
private helperContainer: HTMLElement;
|
||||
private compositionView: HTMLElement;
|
||||
private charSizeStyleElement: HTMLStyleElement;
|
||||
private _parent: HTMLElement;
|
||||
private _context: Window;
|
||||
private _document: Document;
|
||||
private _viewportScrollArea: HTMLElement;
|
||||
private _viewportElement: HTMLElement;
|
||||
private _helperContainer: HTMLElement;
|
||||
private _compositionView: HTMLElement;
|
||||
|
||||
private visualBellTimer: number;
|
||||
private _visualBellTimer: number;
|
||||
|
||||
public browser: IBrowser = <any>Browser;
|
||||
|
||||
public options: ITerminalOptions;
|
||||
private colors: any;
|
||||
|
||||
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
|
||||
public cursorState: number;
|
||||
public cursorHidden: boolean;
|
||||
public convertEol: boolean;
|
||||
|
||||
private sendDataQueue: string;
|
||||
private customKeyEventHandler: CustomKeyEventHandler;
|
||||
private _sendDataQueue: string;
|
||||
private _customKeyEventHandler: CustomKeyEventHandler;
|
||||
|
||||
// modes
|
||||
public applicationKeypad: boolean;
|
||||
@@ -174,10 +169,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public charsets: ICharset[];
|
||||
|
||||
// mouse properties
|
||||
private decLocator: boolean; // This is unstable and never set
|
||||
private _decLocator: boolean; // This is unstable and never set
|
||||
public x10Mouse: boolean;
|
||||
public vt200Mouse: boolean;
|
||||
private vt300Mouse: boolean; // This is unstable and never set
|
||||
private _vt300Mouse: boolean; // This is unstable and never set
|
||||
public normalMouse: boolean;
|
||||
public mouseEvents: boolean;
|
||||
public sendFocus: boolean;
|
||||
@@ -186,14 +181,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public urxvtMouse: boolean;
|
||||
|
||||
// misc
|
||||
private refreshStart: number;
|
||||
private refreshEnd: number;
|
||||
private _refreshStart: number;
|
||||
private _refreshEnd: number;
|
||||
public savedCols: number;
|
||||
|
||||
// stream
|
||||
private readable: boolean;
|
||||
private writable: boolean;
|
||||
|
||||
public defAttr: number;
|
||||
public curAttr: number;
|
||||
|
||||
@@ -204,7 +195,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// user input states
|
||||
public writeBuffer: string[];
|
||||
private writeInProgress: boolean;
|
||||
private _writeInProgress: boolean;
|
||||
|
||||
/**
|
||||
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
|
||||
@@ -212,26 +203,23 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
|
||||
* below threshold.
|
||||
*/
|
||||
private xoffSentToCatchUp: boolean;
|
||||
private _xoffSentToCatchUp: boolean;
|
||||
|
||||
/** Whether writing has been stopped as a result of XOFF */
|
||||
private writeStopped: boolean;
|
||||
|
||||
// leftover surrogate high from previous write invocation
|
||||
private surrogateHigh: string;
|
||||
// private _writeStopped: boolean;
|
||||
|
||||
// Store if user went browsing history in scrollback
|
||||
private userScrolling: boolean;
|
||||
private _userScrolling: boolean;
|
||||
|
||||
private inputHandler: InputHandler;
|
||||
private _inputHandler: InputHandler;
|
||||
public soundManager: SoundManager;
|
||||
private parser: Parser;
|
||||
private _parser: Parser;
|
||||
public renderer: IRenderer;
|
||||
public selectionManager: SelectionManager;
|
||||
public linkifier: ILinkifier;
|
||||
public buffers: BufferSet;
|
||||
public viewport: IViewport;
|
||||
private compositionHelper: ICompositionHelper;
|
||||
private _compositionHelper: ICompositionHelper;
|
||||
public charMeasure: CharMeasure;
|
||||
private _mouseZoneManager: IMouseZoneManager;
|
||||
public mouseHelper: MouseHelper;
|
||||
@@ -258,10 +246,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
) {
|
||||
super();
|
||||
this.options = options;
|
||||
this.setup();
|
||||
this._setup();
|
||||
}
|
||||
|
||||
private setup(): void {
|
||||
private _setup(): void {
|
||||
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
|
||||
if (this.options[key] == null) {
|
||||
this.options[key] = DEFAULT_OPTIONS[key];
|
||||
@@ -273,7 +261,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// this.context = options.context || window;
|
||||
// this.document = options.document || document;
|
||||
// TODO: WHy not document.body?
|
||||
this.parent = document ? document.body : null;
|
||||
this._parent = document ? document.body : null;
|
||||
|
||||
this.cols = this.options.cols;
|
||||
this.rows = this.options.rows;
|
||||
@@ -284,8 +272,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
this.sendDataQueue = '';
|
||||
this.customKeyEventHandler = null;
|
||||
this._sendDataQueue = '';
|
||||
this._customKeyEventHandler = null;
|
||||
|
||||
// modes
|
||||
this.applicationKeypad = false;
|
||||
@@ -302,9 +290,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// TODO: Can this be just []?
|
||||
this.charsets = [null];
|
||||
|
||||
this.readable = true;
|
||||
this.writable = true;
|
||||
|
||||
this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
|
||||
this.curAttr = (0 << 18) | (257 << 9) | (256 << 0);
|
||||
|
||||
@@ -315,15 +300,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// user input states
|
||||
this.writeBuffer = [];
|
||||
this.writeInProgress = false;
|
||||
this._writeInProgress = false;
|
||||
|
||||
this.xoffSentToCatchUp = false;
|
||||
this.writeStopped = false;
|
||||
this.surrogateHigh = '';
|
||||
this.userScrolling = false;
|
||||
this._xoffSentToCatchUp = false;
|
||||
// this._writeStopped = false;
|
||||
this._userScrolling = false;
|
||||
|
||||
this.inputHandler = new InputHandler(this);
|
||||
this.parser = new Parser(this.inputHandler, this);
|
||||
this._inputHandler = new InputHandler(this);
|
||||
this._parser = new Parser(this._inputHandler, this);
|
||||
// Reuse renderer if the Terminal is being recreated via a reset call.
|
||||
this.renderer = this.renderer || null;
|
||||
this.selectionManager = this.selectionManager || null;
|
||||
@@ -472,11 +456,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold' || key === 'enableBold');
|
||||
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
this.renderer.clear();
|
||||
this.renderer.onResize(this.cols, this.rows, didCharSizeChange);
|
||||
this.renderer.onResize(this.cols, this.rows);
|
||||
this.refresh(0, this.rows - 1);
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
@@ -540,8 +522,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
/**
|
||||
* Initialize default behavior
|
||||
*/
|
||||
private initGlobal(): void {
|
||||
this.bindKeys();
|
||||
private _initGlobal(): void {
|
||||
this._bindKeys();
|
||||
|
||||
// Bind clipboard functionality
|
||||
on(this.element, 'copy', (event: ClipboardEvent) => {
|
||||
@@ -587,7 +569,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
/**
|
||||
* Apply key handling to the terminal
|
||||
*/
|
||||
private bindKeys(): void {
|
||||
private _bindKeys(): void {
|
||||
const self = this;
|
||||
on(this.element, 'keydown', function (ev: KeyboardEvent): void {
|
||||
if (document.activeElement !== this) {
|
||||
@@ -611,11 +593,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true);
|
||||
on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true);
|
||||
on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart());
|
||||
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e));
|
||||
on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());
|
||||
this.on('refresh', () => this.compositionHelper.updateCompositionElements());
|
||||
this.on('refresh', (data) => this.queueLinkification(data.start, data.end));
|
||||
on(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart());
|
||||
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e));
|
||||
on(this.textarea, 'compositionend', () => this._compositionHelper.compositionend());
|
||||
this.on('refresh', () => this._compositionHelper.updateCompositionElements());
|
||||
this.on('refresh', (data) => this._queueLinkification(data.start, data.end));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,47 +606,43 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param {HTMLElement} parent The element to create the terminal within.
|
||||
*/
|
||||
public open(parent: HTMLElement): void {
|
||||
let i = 0;
|
||||
let div;
|
||||
this._parent = parent || this._parent;
|
||||
|
||||
this.parent = parent || this.parent;
|
||||
|
||||
if (!this.parent) {
|
||||
if (!this._parent) {
|
||||
throw new Error('Terminal requires a parent element.');
|
||||
}
|
||||
|
||||
// Grab global elements
|
||||
this.context = this.parent.ownerDocument.defaultView;
|
||||
this.document = this.parent.ownerDocument;
|
||||
this.body = <HTMLBodyElement>this.document.body;
|
||||
this._context = this._parent.ownerDocument.defaultView;
|
||||
this._document = this._parent.ownerDocument;
|
||||
|
||||
this._screenDprMonitor = new ScreenDprMonitor();
|
||||
this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio));
|
||||
|
||||
// Create main element container
|
||||
this.element = this.document.createElement('div');
|
||||
this.element = this._document.createElement('div');
|
||||
this.element.classList.add('terminal');
|
||||
this.element.classList.add('xterm');
|
||||
this.element.setAttribute('tabindex', '0');
|
||||
this.parent.appendChild(this.element);
|
||||
this._parent.appendChild(this.element);
|
||||
|
||||
// Performance: Use a document fragment to build the terminal
|
||||
// viewport and helper elements detached from the DOM
|
||||
const fragment = document.createDocumentFragment();
|
||||
this.viewportElement = document.createElement('div');
|
||||
this.viewportElement.classList.add('xterm-viewport');
|
||||
fragment.appendChild(this.viewportElement);
|
||||
this.viewportScrollArea = document.createElement('div');
|
||||
this.viewportScrollArea.classList.add('xterm-scroll-area');
|
||||
this.viewportElement.appendChild(this.viewportScrollArea);
|
||||
this._viewportElement = document.createElement('div');
|
||||
this._viewportElement.classList.add('xterm-viewport');
|
||||
fragment.appendChild(this._viewportElement);
|
||||
this._viewportScrollArea = document.createElement('div');
|
||||
this._viewportScrollArea.classList.add('xterm-scroll-area');
|
||||
this._viewportElement.appendChild(this._viewportScrollArea);
|
||||
|
||||
this.screenElement = document.createElement('div');
|
||||
this.screenElement.classList.add('xterm-screen');
|
||||
// Create the container that will hold helpers like the textarea for
|
||||
// capturing DOM Events. Then produce the helpers.
|
||||
this.helperContainer = document.createElement('div');
|
||||
this.helperContainer.classList.add('xterm-helpers');
|
||||
this.screenElement.appendChild(this.helperContainer);
|
||||
this._helperContainer = document.createElement('div');
|
||||
this._helperContainer.classList.add('xterm-helpers');
|
||||
this.screenElement.appendChild(this._helperContainer);
|
||||
fragment.appendChild(this.screenElement);
|
||||
|
||||
this._mouseZoneManager = new MouseZoneManager(this);
|
||||
@@ -682,34 +660,32 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.textarea.tabIndex = 0;
|
||||
this.textarea.addEventListener('focus', () => this._onTextAreaFocus());
|
||||
this.textarea.addEventListener('blur', () => this._onTextAreaBlur());
|
||||
this.helperContainer.appendChild(this.textarea);
|
||||
this._helperContainer.appendChild(this.textarea);
|
||||
|
||||
this.compositionView = document.createElement('div');
|
||||
this.compositionView.classList.add('composition-view');
|
||||
this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
|
||||
this.helperContainer.appendChild(this.compositionView);
|
||||
this._compositionView = document.createElement('div');
|
||||
this._compositionView.classList.add('composition-view');
|
||||
this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this);
|
||||
this._helperContainer.appendChild(this._compositionView);
|
||||
|
||||
this.charSizeStyleElement = document.createElement('style');
|
||||
this.helperContainer.appendChild(this.charSizeStyleElement);
|
||||
this.charMeasure = new CharMeasure(document, this.helperContainer);
|
||||
this.charMeasure = new CharMeasure(document, this._helperContainer);
|
||||
|
||||
// Performance: Add viewport and helper elements from the fragment
|
||||
this.element.appendChild(fragment);
|
||||
|
||||
this.renderer = new Renderer(this, this.options.theme);
|
||||
this.options.theme = null;
|
||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
||||
this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure);
|
||||
this.viewport.onThemeChanged(this.renderer.colorManager.colors);
|
||||
|
||||
this.on('cursormove', () => this.renderer.onCursorMove());
|
||||
this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false));
|
||||
this.on('resize', () => this.renderer.onResize(this.cols, this.rows));
|
||||
this.on('blur', () => this.renderer.onBlur());
|
||||
this.on('focus', () => this.renderer.onFocus());
|
||||
this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio));
|
||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||
// matchMedia query.
|
||||
window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio));
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows));
|
||||
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
||||
|
||||
this.selectionManager = new SelectionManager(this, this.charMeasure);
|
||||
@@ -727,7 +703,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.viewport.syncScrollArea();
|
||||
this.selectionManager.refresh();
|
||||
});
|
||||
this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
|
||||
this._viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
|
||||
|
||||
this.mouseHelper = new MouseHelper(this.renderer);
|
||||
|
||||
@@ -744,7 +720,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.refresh(0, this.rows - 1);
|
||||
|
||||
// Initialize global actions that need to be taken on the document.
|
||||
this.initGlobal();
|
||||
this._initGlobal();
|
||||
|
||||
// Listen for mouse events and translate
|
||||
// them into terminal mouse protocols.
|
||||
@@ -871,7 +847,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// button: button
|
||||
// });
|
||||
|
||||
if (self.vt300Mouse) {
|
||||
if (self._vt300Mouse) {
|
||||
// NOTE: Unstable.
|
||||
// http://www.vt100.net/docs/vt3xx-gp/chapter15.html
|
||||
button &= 3;
|
||||
@@ -888,7 +864,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.decLocator) {
|
||||
if (self._decLocator) {
|
||||
// NOTE: Unstable.
|
||||
button &= 3;
|
||||
pos.x -= 32;
|
||||
@@ -1031,19 +1007,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
}
|
||||
|
||||
// bind events
|
||||
if (this.normalMouse) on(this.document, 'mousemove', sendMove);
|
||||
if (this.normalMouse) on(this._document, 'mousemove', sendMove);
|
||||
|
||||
// x10 compatibility mode can't send button releases
|
||||
if (!this.x10Mouse) {
|
||||
const handler = (ev: MouseEvent) => {
|
||||
sendButton(ev);
|
||||
// TODO: Seems dangerous calling this on document?
|
||||
if (this.normalMouse) off(this.document, 'mousemove', sendMove);
|
||||
off(this.document, 'mouseup', handler);
|
||||
if (this.normalMouse) off(this._document, 'mousemove', sendMove);
|
||||
off(this._document, 'mouseup', handler);
|
||||
return this.cancel(ev);
|
||||
};
|
||||
// TODO: Seems dangerous calling this on document?
|
||||
on(this.document, 'mouseup', handler);
|
||||
on(this._document, 'mouseup', handler);
|
||||
}
|
||||
|
||||
return this.cancel(ev);
|
||||
@@ -1054,8 +1030,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// }
|
||||
|
||||
on(el, 'wheel', (ev: WheelEvent) => {
|
||||
if (!this.mouseEvents) return;
|
||||
if (this.x10Mouse || this.vt300Mouse || this.decLocator) return;
|
||||
if (!this.mouseEvents) {
|
||||
// Convert wheel events into up/down events when the buffer does not have scrollback, this
|
||||
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
|
||||
if (!this.buffer.hasScrollback) {
|
||||
const amount = this.viewport.getLinesScrolled(ev);
|
||||
|
||||
// Do nothing if there's no vertical scroll
|
||||
if (amount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct and send sequences
|
||||
const sequence = C0.ESC + (this.applicationCursor ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
|
||||
let data = '';
|
||||
for (let i = 0; i < Math.abs(amount); i++) {
|
||||
data += sequence;
|
||||
}
|
||||
this.send(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.x10Mouse || this._vt300Mouse || this._decLocator) return;
|
||||
sendButton(ev);
|
||||
ev.preventDefault();
|
||||
});
|
||||
@@ -1086,8 +1082,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
*/
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.readable = false;
|
||||
this.writable = false;
|
||||
this.handler = () => {};
|
||||
this.write = () => {};
|
||||
if (this.element && this.element.parentNode) {
|
||||
@@ -1113,7 +1107,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param {number} start The row to start from (between 0 and this.rows - 1).
|
||||
* @param {number} end The row to end at (between start and this.rows - 1).
|
||||
*/
|
||||
private queueLinkification(start: number, end: number): void {
|
||||
private _queueLinkification(start: number, end: number): void {
|
||||
if (this.linkifier) {
|
||||
this.linkifier.linkifyRows(start, end);
|
||||
}
|
||||
@@ -1153,13 +1147,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
if (!willBufferBeTrimmed) {
|
||||
this.buffer.ybase++;
|
||||
// Only scroll the ydisp with ybase if the user has not scrolled up
|
||||
if (!this.userScrolling) {
|
||||
if (!this._userScrolling) {
|
||||
this.buffer.ydisp++;
|
||||
}
|
||||
} else {
|
||||
// When the buffer is full and the user has scrolled up, keep the text
|
||||
// stable unless ydisp is right at the top
|
||||
if (this.userScrolling) {
|
||||
if (this._userScrolling) {
|
||||
this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1167,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// Move the viewport to the bottom of the buffer unless the user is
|
||||
// scrolling.
|
||||
if (!this.userScrolling) {
|
||||
if (!this._userScrolling) {
|
||||
this.buffer.ydisp = this.buffer.ybase;
|
||||
}
|
||||
|
||||
@@ -1202,9 +1196,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
if (this.buffer.ydisp === 0) {
|
||||
return;
|
||||
}
|
||||
this.userScrolling = true;
|
||||
this._userScrolling = true;
|
||||
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
|
||||
this.userScrolling = false;
|
||||
this._userScrolling = false;
|
||||
}
|
||||
|
||||
const oldYdisp = this.buffer.ydisp;
|
||||
@@ -1254,54 +1248,54 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// Send XOFF to pause the pty process if the write buffer becomes too large so
|
||||
// xterm.js can catch up before more data is sent. This is necessary in order
|
||||
// to keep signals such as ^C responsive.
|
||||
if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
||||
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
|
||||
// XOFF - stop pty pipe
|
||||
// XON will be triggered by emulator before processing data chunk
|
||||
this.send(C0.DC3);
|
||||
this.xoffSentToCatchUp = true;
|
||||
this._xoffSentToCatchUp = true;
|
||||
}
|
||||
|
||||
if (!this.writeInProgress && this.writeBuffer.length > 0) {
|
||||
if (!this._writeInProgress && this.writeBuffer.length > 0) {
|
||||
// Kick off a write which will write all data in sequence recursively
|
||||
this.writeInProgress = true;
|
||||
this._writeInProgress = true;
|
||||
// Kick off an async innerWrite so more writes can come in while processing data
|
||||
setTimeout(() => {
|
||||
this.innerWrite();
|
||||
this._innerWrite();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private innerWrite(): void {
|
||||
private _innerWrite(): void {
|
||||
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
|
||||
while (writeBatch.length > 0) {
|
||||
const data = writeBatch.shift();
|
||||
|
||||
// If XOFF was sent in order to catch up with the pty process, resume it if
|
||||
// the writeBuffer is empty to allow more data to come in.
|
||||
if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
|
||||
if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
|
||||
this.send(C0.DC1);
|
||||
this.xoffSentToCatchUp = false;
|
||||
this._xoffSentToCatchUp = false;
|
||||
}
|
||||
|
||||
this.refreshStart = this.buffer.y;
|
||||
this.refreshEnd = this.buffer.y;
|
||||
this._refreshStart = this.buffer.y;
|
||||
this._refreshEnd = this.buffer.y;
|
||||
|
||||
// HACK: Set the parser state based on it's state at the time of return.
|
||||
// This works around the bug #662 which saw the parser state reset in the
|
||||
// middle of parsing escape sequence in two chunks. For some reason the
|
||||
// state of the parser resets to 0 after exiting parser.parse. This change
|
||||
// just sets the state back based on the correct return statement.
|
||||
const state = this.parser.parse(data);
|
||||
this.parser.setState(state);
|
||||
const state = this._parser.parse(data);
|
||||
this._parser.setState(state);
|
||||
|
||||
this.updateRange(this.buffer.y);
|
||||
this.refresh(this.refreshStart, this.refreshEnd);
|
||||
this.refresh(this._refreshStart, this._refreshEnd);
|
||||
}
|
||||
if (this.writeBuffer.length > 0) {
|
||||
// Allow renderer to catch up before processing the next batch
|
||||
setTimeout(() => this.innerWrite(), 0);
|
||||
setTimeout(() => this._innerWrite(), 0);
|
||||
} else {
|
||||
this.writeInProgress = false;
|
||||
this._writeInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,37 +1317,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* the event should be processed by xterm.js.
|
||||
*/
|
||||
public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
|
||||
this.customKeyEventHandler = customKeyEventHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a http(s) link handler, forcing web links to behave differently to
|
||||
* regular <a> tags. This will trigger a refresh as links potentially need to be
|
||||
* reconstructed. Calling this with null will remove the handler.
|
||||
* @param handler The handler callback function.
|
||||
*/
|
||||
public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
|
||||
if (!this.linkifier) {
|
||||
throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');
|
||||
}
|
||||
this.linkifier.setHypertextLinkHandler(handler);
|
||||
// Refresh to force links to refresh
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a validation callback for hypertext links. This is useful to use
|
||||
* validation logic or to do something with the link's element and url.
|
||||
* @param callback The callback to use, this can
|
||||
* be cleared with null.
|
||||
*/
|
||||
public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
|
||||
if (!this.linkifier) {
|
||||
throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
|
||||
}
|
||||
this.linkifier.setHypertextValidationCallback(callback);
|
||||
// // Refresh to force links to refresh
|
||||
this.refresh(0, this.rows - 1);
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1367,12 +1331,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @return The ID of the new matcher, this can be used to deregister.
|
||||
*/
|
||||
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {
|
||||
if (this.linkifier) {
|
||||
const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
|
||||
this.refresh(0, this.rows - 1);
|
||||
return matcherId;
|
||||
}
|
||||
return 0;
|
||||
const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
|
||||
this.refresh(0, this.rows - 1);
|
||||
return matcherId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1380,10 +1341,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param matcherId The link matcher's ID (returned after register)
|
||||
*/
|
||||
public deregisterLinkMatcher(matcherId: number): void {
|
||||
if (this.linkifier) {
|
||||
if (this.linkifier.deregisterLinkMatcher(matcherId)) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
if (this.linkifier.deregisterLinkMatcher(matcherId)) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1427,11 +1386,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param {KeyboardEvent} ev The keydown event to be handled.
|
||||
*/
|
||||
protected _keyDown(ev: KeyboardEvent): boolean {
|
||||
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
|
||||
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.compositionHelper.keydown(ev)) {
|
||||
if (!this._compositionHelper.keydown(ev)) {
|
||||
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
@@ -1440,11 +1399,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
const result = this._evaluateKeyEscapeSequence(ev);
|
||||
|
||||
if (result.key === C0.DC3) { // XOFF
|
||||
this.writeStopped = true;
|
||||
} else if (result.key === C0.DC1) { // XON
|
||||
this.writeStopped = false;
|
||||
}
|
||||
// if (result.key === C0.DC3) { // XOFF
|
||||
// this._writeStopped = true;
|
||||
// } else if (result.key === C0.DC1) { // XON
|
||||
// this._writeStopped = false;
|
||||
// }
|
||||
|
||||
if (result.scrollLines) {
|
||||
this.scrollLines(result.scrollLines);
|
||||
@@ -1838,7 +1797,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
protected _keyPress(ev: KeyboardEvent): boolean {
|
||||
let key;
|
||||
|
||||
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
|
||||
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1875,14 +1834,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param {string} data
|
||||
*/
|
||||
public send(data: string): void {
|
||||
if (!this.sendDataQueue) {
|
||||
if (!this._sendDataQueue) {
|
||||
setTimeout(() => {
|
||||
this.handler(this.sendDataQueue);
|
||||
this.sendDataQueue = '';
|
||||
this.handler(this._sendDataQueue);
|
||||
this._sendDataQueue = '';
|
||||
}, 1);
|
||||
}
|
||||
|
||||
this.sendDataQueue += data;
|
||||
this._sendDataQueue += data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1891,14 +1850,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
*/
|
||||
public bell(): void {
|
||||
this.emit('bell');
|
||||
if (this.soundBell()) {
|
||||
if (this._soundBell()) {
|
||||
this.soundManager.playBellSound();
|
||||
}
|
||||
|
||||
if (this.visualBell()) {
|
||||
if (this._visualBell()) {
|
||||
this.element.classList.add('visual-bell-active');
|
||||
clearTimeout(this.visualBellTimer);
|
||||
this.visualBellTimer = window.setTimeout(() => {
|
||||
clearTimeout(this._visualBellTimer);
|
||||
this._visualBellTimer = window.setTimeout(() => {
|
||||
this.element.classList.remove('visual-bell-active');
|
||||
}, 200);
|
||||
}
|
||||
@@ -1909,8 +1868,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
*/
|
||||
public log(text: string, data?: any): void {
|
||||
if (!this.options.debug) return;
|
||||
if (!this.context.console || !this.context.console.log) return;
|
||||
this.context.console.log(text, data);
|
||||
if (!this._context.console || !this._context.console.log) return;
|
||||
this._context.console.log(text, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1918,8 +1877,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
*/
|
||||
public error(text: string, data?: any): void {
|
||||
if (!this.options.debug) return;
|
||||
if (!this.context.console || !this.context.console.error) return;
|
||||
this.context.console.error(text, data);
|
||||
if (!this._context.console || !this._context.console.error) return;
|
||||
this._context.console.error(text, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1963,8 +1922,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* @param {number} y The number of rows to refresh next.
|
||||
*/
|
||||
public updateRange(y: number): void {
|
||||
if (y < this.refreshStart) this.refreshStart = y;
|
||||
if (y > this.refreshEnd) this.refreshEnd = y;
|
||||
if (y < this._refreshStart) this._refreshStart = y;
|
||||
if (y > this._refreshEnd) this._refreshEnd = y;
|
||||
// if (y > this.refreshEnd) {
|
||||
// this.refreshEnd = y;
|
||||
// if (y > this.rows - 1) {
|
||||
@@ -1977,8 +1936,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* Set the range of refreshing to the maximum value
|
||||
*/
|
||||
public maxRange(): void {
|
||||
this.refreshStart = 0;
|
||||
this.refreshEnd = this.rows - 1;
|
||||
this._refreshStart = 0;
|
||||
this._refreshEnd = this.rows - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2116,7 +2075,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* Emit the 'title' event and populate the given title.
|
||||
* @param {string} title The title to populate in the event.
|
||||
*/
|
||||
private handleTitle(title: string): void {
|
||||
public handleTitle(title: string): void {
|
||||
/**
|
||||
* This event is emitted when the title of the terminal is changed
|
||||
* from inside the terminal. The parameter is the new title.
|
||||
@@ -2171,11 +2130,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public reset(): void {
|
||||
this.options.rows = this.rows;
|
||||
this.options.cols = this.cols;
|
||||
const customKeyEventHandler = this.customKeyEventHandler;
|
||||
const inputHandler = this.inputHandler;
|
||||
this.setup();
|
||||
this.customKeyEventHandler = customKeyEventHandler;
|
||||
this.inputHandler = inputHandler;
|
||||
const customKeyEventHandler = this._customKeyEventHandler;
|
||||
const inputHandler = this._inputHandler;
|
||||
this._setup();
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
this._inputHandler = inputHandler;
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
@@ -2184,7 +2143,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
/**
|
||||
* ESC H Tab Set (HTS is 0x88).
|
||||
*/
|
||||
private tabSet(): void {
|
||||
public tabSet(): void {
|
||||
this.buffer.tabs[this.buffer.x] = true;
|
||||
}
|
||||
|
||||
@@ -2203,13 +2162,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return matchColor_(r1, g1, b1);
|
||||
}
|
||||
|
||||
private visualBell(): boolean {
|
||||
private _visualBell(): boolean {
|
||||
return false;
|
||||
// return this.options.bellStyle === 'visual' ||
|
||||
// this.options.bellStyle === 'both';
|
||||
}
|
||||
|
||||
private soundBell(): boolean {
|
||||
private _soundBell(): boolean {
|
||||
return this.options.bellStyle === 'sound';
|
||||
// return this.options.bellStyle === 'sound' ||
|
||||
// this.options.bellStyle === 'both';
|
||||
|
||||
+5
-4
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter as IPublicEventEmitter, IEventEmitter } from 'xterm';
|
||||
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm';
|
||||
import { IColorSet, IRenderer } from './renderer/Types';
|
||||
import { IMouseZoneManager } from './input/Types';
|
||||
|
||||
@@ -14,7 +14,7 @@ export type XtermListener = (...args: any[]) => void;
|
||||
export type CharData = [number, string, number, number];
|
||||
export type LineData = CharData[];
|
||||
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
export enum LinkHoverEventTypes {
|
||||
@@ -84,11 +84,13 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
matchColor(r1: number, g1: number, b1: number): number;
|
||||
error(text: string, data?: any): void;
|
||||
setOption(key: string, value: any): void;
|
||||
tabSet(): void;
|
||||
}
|
||||
|
||||
export interface IViewport {
|
||||
scrollBarWidth: number;
|
||||
syncScrollArea(): void;
|
||||
getLinesScrolled(ev: WheelEvent): number;
|
||||
onWheel(ev: WheelEvent): void;
|
||||
onTouchStart(ev: TouchEvent): void;
|
||||
onTouchMove(ev: TouchEvent): void;
|
||||
@@ -252,6 +254,7 @@ export interface IBuffer {
|
||||
tabs: any;
|
||||
scrollBottom: number;
|
||||
scrollTop: number;
|
||||
hasScrollback: boolean;
|
||||
savedY: number;
|
||||
savedX: number;
|
||||
isCursorInViewport: boolean;
|
||||
@@ -297,8 +300,6 @@ export interface ISelectionManager {
|
||||
export interface ILinkifier extends IEventEmitter {
|
||||
attachToDom(mouseZoneManager: IMouseZoneManager): void;
|
||||
linkifyRows(start: number, end: number): void;
|
||||
setHypertextLinkHandler(handler: LinkMatcherHandler): void;
|
||||
setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void;
|
||||
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
|
||||
+91
-53
@@ -15,51 +15,56 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
|
||||
*/
|
||||
export class Viewport implements IViewport {
|
||||
public scrollBarWidth: number = 0;
|
||||
private currentRowHeight: number = 0;
|
||||
private lastRecordedBufferLength: number = 0;
|
||||
private lastRecordedViewportHeight: number = 0;
|
||||
private lastRecordedBufferHeight: number = 0;
|
||||
private lastTouchY: number;
|
||||
private _currentRowHeight: number = 0;
|
||||
private _lastRecordedBufferLength: number = 0;
|
||||
private _lastRecordedViewportHeight: number = 0;
|
||||
private _lastRecordedBufferHeight: number = 0;
|
||||
private _lastTouchY: number;
|
||||
|
||||
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
|
||||
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
|
||||
// quick fix and could have a more robust solution in place that reset the value when needed.
|
||||
private _wheelPartialScroll: number = 0;
|
||||
|
||||
/**
|
||||
* Creates a new Viewport.
|
||||
* @param terminal The terminal this viewport belongs to.
|
||||
* @param viewportElement The DOM element acting as the viewport.
|
||||
* @param scrollArea The DOM element acting as the scroll area.
|
||||
* @param charMeasure A DOM element used to measure the character size of. the terminal.
|
||||
* @param _terminal The terminal this viewport belongs to.
|
||||
* @param _viewportElement The DOM element acting as the viewport.
|
||||
* @param _scrollArea The DOM element acting as the scroll area.
|
||||
* @param _charMeasure A DOM element used to measure the character size of. the terminal.
|
||||
*/
|
||||
constructor(
|
||||
private terminal: ITerminal,
|
||||
private viewportElement: HTMLElement,
|
||||
private scrollArea: HTMLElement,
|
||||
private charMeasure: CharMeasure
|
||||
private _terminal: ITerminal,
|
||||
private _viewportElement: HTMLElement,
|
||||
private _scrollArea: HTMLElement,
|
||||
private _charMeasure: CharMeasure
|
||||
) {
|
||||
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
|
||||
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
|
||||
// therefore we account for a standard amount to make it visible
|
||||
this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
this._viewportElement.addEventListener('scroll', this._onScroll.bind(this));
|
||||
|
||||
// Perform this async to ensure the CharMeasure is ready.
|
||||
setTimeout(() => this.syncScrollArea(), 0);
|
||||
}
|
||||
|
||||
public onThemeChanged(colors: IColorSet): void {
|
||||
this.viewportElement.style.backgroundColor = colors.background;
|
||||
this._viewportElement.style.backgroundColor = colors.background;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||
* necessary.
|
||||
*/
|
||||
private refresh(): void {
|
||||
if (this.charMeasure.height > 0) {
|
||||
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
|
||||
this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
|
||||
const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight);
|
||||
if (this.lastRecordedBufferHeight !== newBufferHeight) {
|
||||
this.lastRecordedBufferHeight = newBufferHeight;
|
||||
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
|
||||
private _refresh(): void {
|
||||
if (this._charMeasure.height > 0) {
|
||||
this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
|
||||
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
|
||||
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight);
|
||||
if (this._lastRecordedBufferHeight !== newBufferHeight) {
|
||||
this._lastRecordedBufferHeight = newBufferHeight;
|
||||
this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,24 +73,24 @@ export class Viewport implements IViewport {
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(): void {
|
||||
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
|
||||
if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) {
|
||||
// If buffer height changed
|
||||
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
|
||||
this.refresh();
|
||||
} else if (this.lastRecordedViewportHeight !== (<any>this.terminal).renderer.dimensions.canvasHeight) {
|
||||
this._lastRecordedBufferLength = this._terminal.buffer.lines.length;
|
||||
this._refresh();
|
||||
} else if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
|
||||
// If viewport height changed
|
||||
this.refresh();
|
||||
this._refresh();
|
||||
} else {
|
||||
// If size has changed, refresh viewport
|
||||
if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {
|
||||
this.refresh();
|
||||
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
this._refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Sync scrollTop
|
||||
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
|
||||
if (this.viewportElement.scrollTop !== scrollTop) {
|
||||
this.viewportElement.scrollTop = scrollTop;
|
||||
const scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;
|
||||
if (this._viewportElement.scrollTop !== scrollTop) {
|
||||
this._viewportElement.scrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,16 +99,16 @@ export class Viewport implements IViewport {
|
||||
* terminal to scroll to it.
|
||||
* @param ev The scroll event.
|
||||
*/
|
||||
private onScroll(ev: Event): void {
|
||||
private _onScroll(ev: Event): void {
|
||||
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
|
||||
// which causes the terminal to scroll the buffer to the top
|
||||
if (!this.viewportElement.offsetParent) {
|
||||
if (!this._viewportElement.offsetParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
||||
const diff = newRow - this.terminal.buffer.ydisp;
|
||||
this.terminal.scrollLines(diff, true);
|
||||
const newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight);
|
||||
const diff = newRow - this._terminal.buffer.ydisp;
|
||||
this._terminal.scrollLines(diff, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,28 +118,61 @@ export class Viewport implements IViewport {
|
||||
* @param ev The mouse wheel event.
|
||||
*/
|
||||
public onWheel(ev: WheelEvent): void {
|
||||
if (ev.deltaY === 0) {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
const amount = this._getPixelsScrolled(ev);
|
||||
if (amount === 0) {
|
||||
return;
|
||||
}
|
||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||
let multiplier = 1;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
multiplier = this.currentRowHeight;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
multiplier = this.currentRowHeight * this.terminal.rows;
|
||||
}
|
||||
this.viewportElement.scrollTop += ev.deltaY * multiplier;
|
||||
this._viewportElement.scrollTop += amount;
|
||||
// Prevent the page from scrolling when the terminal scrolls
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private _getPixelsScrolled(ev: WheelEvent): number {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
if (ev.deltaY === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||
let amount = ev.deltaY;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
amount *= this._currentRowHeight;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
amount *= this._currentRowHeight * this._terminal.rows;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of pixels scrolled by the mouse event taking into account what type of delta
|
||||
* is being used.
|
||||
* @param ev The mouse wheel event.
|
||||
*/
|
||||
public getLinesScrolled(ev: WheelEvent): number {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
if (ev.deltaY === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fallback to WheelEvent.DOM_DELTA_LINE
|
||||
let amount = ev.deltaY;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
|
||||
amount /= this._currentRowHeight + 0.0; // Prevent integer division
|
||||
this._wheelPartialScroll += amount;
|
||||
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
|
||||
this._wheelPartialScroll %= 1;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
amount *= this._terminal.rows;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the touchstart event, recording the touch occurred.
|
||||
* @param ev The touch event.
|
||||
*/
|
||||
public onTouchStart(ev: TouchEvent): void {
|
||||
this.lastTouchY = ev.touches[0].pageY;
|
||||
this._lastTouchY = ev.touches[0].pageY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,12 +180,12 @@ export class Viewport implements IViewport {
|
||||
* @param ev The touch event.
|
||||
*/
|
||||
public onTouchMove(ev: TouchEvent): void {
|
||||
let deltaY = this.lastTouchY - ev.touches[0].pageY;
|
||||
this.lastTouchY = ev.touches[0].pageY;
|
||||
let deltaY = this._lastTouchY - ev.touches[0].pageY;
|
||||
this._lastTouchY = ev.touches[0].pageY;
|
||||
if (deltaY === 0) {
|
||||
return;
|
||||
}
|
||||
this.viewportElement.scrollTop += deltaY;
|
||||
this._viewportElement.scrollTop += deltaY;
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.weblinks",
|
||||
"main": "weblinks.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/webLinks/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
|
||||
import * as webLinks from './webLinks';
|
||||
|
||||
class MockTerminal {
|
||||
public regex: RegExp;
|
||||
public handler: (event: MouseEvent, uri: string) => void;
|
||||
public options?: any;
|
||||
|
||||
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number {
|
||||
this.regex = regex;
|
||||
this.handler = handler;
|
||||
this.options = options;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
describe('webLinks addon', () => {
|
||||
describe('apply', () => {
|
||||
it('should do register the `webLinksInit` method', () => {
|
||||
webLinks.apply(<any>MockTerminal);
|
||||
assert.equal(typeof (<any>MockTerminal).prototype.webLinksInit, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow ~ character in URI path', () => {
|
||||
const term = new MockTerminal();
|
||||
webLinks.webLinksInit(<any>term);
|
||||
|
||||
const row = ' http://foo.com/a~b#c~d?e~f ';
|
||||
|
||||
let match = row.match(term.regex);
|
||||
let uri = match[term.options.matchIndex];
|
||||
|
||||
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal, ILinkMatcherOptions } from 'xterm';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
const domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
const tldClause = '([a-z\\.]{2,6})';
|
||||
const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
const start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
const end = ')($|' + negatedPathCharacterSet + ')';
|
||||
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
|
||||
function handleLink(event: MouseEvent, uri: string): void {
|
||||
window.open(uri, '_blank');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the web links addon, registering the link matcher.
|
||||
* @param term The terminal to use web links within.
|
||||
* @param handler A custom handler to use.
|
||||
* @param options Custom options to use, matchIndex will always be ignored.
|
||||
*/
|
||||
export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {
|
||||
options.matchIndex = 1;
|
||||
term.registerLinkMatcher(strictUrlRegex, handler, options);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {
|
||||
webLinksInit(this, handler, options);
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "xterm.winptyCompat",
|
||||
"name": "xterm.winptycompat",
|
||||
"main": "winptyCompat.js",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -56,9 +56,13 @@ export class AltClickHandler {
|
||||
* then moves to requested col.
|
||||
*/
|
||||
private _arrowSequences(): string {
|
||||
return this._resetStartingRow() +
|
||||
this._moveToRequestedRow() +
|
||||
this._moveToRequestedCol();
|
||||
// The alt buffer should try to navigate between rows
|
||||
if (!this._terminal.buffer.hasScrollback) {
|
||||
return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol();
|
||||
}
|
||||
|
||||
// Only move horizontally for the normal buffer
|
||||
return this._moveHorizontallyOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +71,6 @@ export class AltClickHandler {
|
||||
* positioning.
|
||||
*/
|
||||
private _resetStartingRow(): string {
|
||||
let startRow = this._endRow - this._wrappedRowsForRow(this._endRow);
|
||||
let endRow = this._endRow;
|
||||
|
||||
if (this._moveToRequestedRow().length === 0) {
|
||||
return '';
|
||||
} else {
|
||||
@@ -113,6 +114,11 @@ export class AltClickHandler {
|
||||
).length, this._sequence(direction));
|
||||
}
|
||||
|
||||
private _moveHorizontallyOnly(): string {
|
||||
let direction = this._horizontalDirection();
|
||||
return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
|
||||
@@ -4,17 +4,22 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as Terminal from '../Terminal';
|
||||
import * as Clipboard from './Clipboard';
|
||||
|
||||
describe('evaluatePastedTextProcessing', () => {
|
||||
it('should replace carriage return + line feed with line feed on windows', () => {
|
||||
const pastedText = 'foo\r\nbar\r\n';
|
||||
const processedText = Clipboard.prepareTextForTerminal(pastedText, false);
|
||||
const windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true);
|
||||
it('should replace carriage return and/or line feed with carriage return', () => {
|
||||
const pastedText = {
|
||||
unix: 'foo\nbar\n',
|
||||
windows: 'foo\r\nbar\r\n'
|
||||
};
|
||||
|
||||
assert.equal(processedText, 'foo\r\nbar\r\n');
|
||||
assert.equal(windowsProcessedText, 'foo\rbar\r');
|
||||
const processedText = {
|
||||
unix: Clipboard.prepareTextForTerminal(pastedText.unix),
|
||||
windows: Clipboard.prepareTextForTerminal(pastedText.windows)
|
||||
};
|
||||
|
||||
assert.equal(processedText.unix, 'foo\rbar\r');
|
||||
assert.equal(processedText.windows, 'foo\rbar\r');
|
||||
});
|
||||
it('should bracket pasted text in bracketedPasteMode', () => {
|
||||
const pastedText = 'foo bar';
|
||||
|
||||
@@ -18,11 +18,8 @@ declare var window: IWindow;
|
||||
* Prepares text to be pasted into the terminal by normalizing the line endings
|
||||
* @param text The pasted text that needs processing before inserting into the terminal
|
||||
*/
|
||||
export function prepareTextForTerminal(text: string, isMSWindows: boolean): string {
|
||||
if (isMSWindows) {
|
||||
return text.replace(/\r?\n/g, '\r');
|
||||
}
|
||||
return text;
|
||||
export function prepareTextForTerminal(text: string): string {
|
||||
return text.replace(/\r?\n/g, '\r');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +59,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
|
||||
let text: string;
|
||||
|
||||
let dispatchPaste = function(text: string): void {
|
||||
text = prepareTextForTerminal(text, term.browser.isMSWindows);
|
||||
text = prepareTextForTerminal(text);
|
||||
text = bracketTextForPaste(text, term.bracketedPasteMode);
|
||||
term.handler(text);
|
||||
term.textarea.value = '';
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
*/
|
||||
|
||||
import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
|
||||
import { CharData, ITerminal, ITerminalOptions } from '../Types';
|
||||
import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas';
|
||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
|
||||
export const INVERTED_DEFAULT_COLOR = -1;
|
||||
const DIM_OPACITY = 0.5;
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { CHAR_ATLAS_CELL_SPACING } from '../shared/atlas/Types';
|
||||
import { acquireCharAtlas } from './atlas/CharAtlas';
|
||||
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -93,7 +92,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||
this._scaledCellWidth = dim.scaledCellWidth;
|
||||
this._scaledCellHeight = dim.scaledCellHeight;
|
||||
this._scaledCharWidth = dim.scaledCharWidth;
|
||||
@@ -110,9 +109,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this.clearAll();
|
||||
}
|
||||
|
||||
if (charSizeChanged) {
|
||||
this._refreshCharAtlas(terminal, this._colors);
|
||||
}
|
||||
this._refreshCharAtlas(terminal, this._colors);
|
||||
}
|
||||
|
||||
public abstract reset(terminal: ITerminal): void;
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../Types';
|
||||
import { IColorSet } from './Types';
|
||||
import { isFirefox } from '../shared/utils/Browser';
|
||||
import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator';
|
||||
|
||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
|
||||
interface ICharAtlasConfig {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: string;
|
||||
fontWeightBold: string;
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
allowTransparency: boolean;
|
||||
colors: IColorSet;
|
||||
}
|
||||
|
||||
interface ICharAtlasCacheEntry {
|
||||
bitmap: HTMLCanvasElement | Promise<ImageBitmap>;
|
||||
config: ICharAtlasConfig;
|
||||
ownedBy: ITerminal[];
|
||||
}
|
||||
|
||||
let charAtlasCache: ICharAtlasCacheEntry[] = [];
|
||||
|
||||
/**
|
||||
* Acquires a char atlas, either generating a new one or returning an existing
|
||||
* one that is in use by another terminal.
|
||||
* @param terminal The terminal.
|
||||
* @param colors The colors to use.
|
||||
*/
|
||||
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||
|
||||
// Check to see if the terminal already owns this config
|
||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||
const entry = charAtlasCache[i];
|
||||
const ownedByIndex = entry.ownedBy.indexOf(terminal);
|
||||
if (ownedByIndex >= 0) {
|
||||
if (configEquals(entry.config, newConfig)) {
|
||||
return entry.bitmap;
|
||||
} else {
|
||||
// The configs differ, release the terminal from the entry
|
||||
if (entry.ownedBy.length === 1) {
|
||||
charAtlasCache.splice(i, 1);
|
||||
} else {
|
||||
entry.ownedBy.splice(ownedByIndex, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try match a char atlas from the cache
|
||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||
const entry = charAtlasCache[i];
|
||||
if (configEquals(entry.config, newConfig)) {
|
||||
// Add the terminal to the cache entry and return
|
||||
entry.ownedBy.push(terminal);
|
||||
return entry.bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
const canvasFactory = (width: number, height: number) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
const charAtlasConfig: ICharAtlasRequest = {
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
background: colors.background,
|
||||
foreground: colors.foreground,
|
||||
ansiColors: colors.ansi,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
allowTransparency: terminal.options.allowTransparency
|
||||
};
|
||||
|
||||
const newEntry: ICharAtlasCacheEntry = {
|
||||
bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig),
|
||||
config: newConfig,
|
||||
ownedBy: [terminal]
|
||||
};
|
||||
charAtlasCache.push(newEntry);
|
||||
return newEntry.bitmap;
|
||||
}
|
||||
|
||||
function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
||||
const clonedColors = {
|
||||
foreground: colors.foreground,
|
||||
background: colors.background,
|
||||
cursor: null,
|
||||
cursorAccent: null,
|
||||
selection: null,
|
||||
ansi: colors.ansi.slice(0, 16)
|
||||
};
|
||||
return {
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
allowTransparency: terminal.options.allowTransparency,
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
|
||||
function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
||||
for (let i = 0; i < a.colors.ansi.length; i++) {
|
||||
if (a.colors.ansi[i] !== b.colors.ansi[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
a.fontWeightBold === b.fontWeightBold &&
|
||||
a.allowTransparency === b.allowTransparency &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
@@ -3,14 +3,21 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { ColorManager } from './ColorManager';
|
||||
|
||||
describe('ColorManager', () => {
|
||||
let cm: ColorManager;
|
||||
let dom: jsdom.JSDOM;
|
||||
let document: Document;
|
||||
let window: Window;
|
||||
|
||||
beforeEach(() => {
|
||||
cm = new ColorManager();
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
cm = new ColorManager(document);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
|
||||
@@ -67,8 +67,10 @@ function toPaddedHex(c: number): string {
|
||||
*/
|
||||
export class ColorManager implements IColorManager {
|
||||
public colors: IColorSet;
|
||||
private _document: Document;
|
||||
|
||||
constructor() {
|
||||
constructor(document: Document) {
|
||||
this._document = document;
|
||||
this.colors = {
|
||||
foreground: DEFAULT_FOREGROUND,
|
||||
background: DEFAULT_BACKGROUND,
|
||||
@@ -85,26 +87,54 @@ export class ColorManager implements IColorManager {
|
||||
* colors will be used where colors are not defined.
|
||||
*/
|
||||
public setTheme(theme: ITheme): void {
|
||||
this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;
|
||||
this.colors.background = theme.background || DEFAULT_BACKGROUND;
|
||||
this.colors.cursor = theme.cursor || DEFAULT_CURSOR;
|
||||
this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;
|
||||
this.colors.selection = theme.selection || DEFAULT_SELECTION;
|
||||
this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0];
|
||||
this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1];
|
||||
this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2];
|
||||
this.colors.ansi[3] = theme.yellow || DEFAULT_ANSI_COLORS[3];
|
||||
this.colors.ansi[4] = theme.blue || DEFAULT_ANSI_COLORS[4];
|
||||
this.colors.ansi[5] = theme.magenta || DEFAULT_ANSI_COLORS[5];
|
||||
this.colors.ansi[6] = theme.cyan || DEFAULT_ANSI_COLORS[6];
|
||||
this.colors.ansi[7] = theme.white || DEFAULT_ANSI_COLORS[7];
|
||||
this.colors.ansi[8] = theme.brightBlack || DEFAULT_ANSI_COLORS[8];
|
||||
this.colors.ansi[9] = theme.brightRed || DEFAULT_ANSI_COLORS[9];
|
||||
this.colors.ansi[10] = theme.brightGreen || DEFAULT_ANSI_COLORS[10];
|
||||
this.colors.ansi[11] = theme.brightYellow || DEFAULT_ANSI_COLORS[11];
|
||||
this.colors.ansi[12] = theme.brightBlue || DEFAULT_ANSI_COLORS[12];
|
||||
this.colors.ansi[13] = theme.brightMagenta || DEFAULT_ANSI_COLORS[13];
|
||||
this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];
|
||||
this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];
|
||||
this.colors.foreground = this._validateColor(theme.foreground, DEFAULT_FOREGROUND);
|
||||
this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND);
|
||||
this.colors.cursor = this._validateColor(theme.cursor, DEFAULT_CURSOR);
|
||||
this.colors.cursorAccent = this._validateColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT);
|
||||
this.colors.selection = this._validateColor(theme.selection, DEFAULT_SELECTION);
|
||||
this.colors.ansi[0] = this._validateColor(theme.black, DEFAULT_ANSI_COLORS[0]);
|
||||
this.colors.ansi[1] = this._validateColor(theme.red, DEFAULT_ANSI_COLORS[1]);
|
||||
this.colors.ansi[2] = this._validateColor(theme.green, DEFAULT_ANSI_COLORS[2]);
|
||||
this.colors.ansi[3] = this._validateColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);
|
||||
this.colors.ansi[4] = this._validateColor(theme.blue, DEFAULT_ANSI_COLORS[4]);
|
||||
this.colors.ansi[5] = this._validateColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);
|
||||
this.colors.ansi[6] = this._validateColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);
|
||||
this.colors.ansi[7] = this._validateColor(theme.white, DEFAULT_ANSI_COLORS[7]);
|
||||
this.colors.ansi[8] = this._validateColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);
|
||||
this.colors.ansi[9] = this._validateColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);
|
||||
this.colors.ansi[10] = this._validateColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);
|
||||
this.colors.ansi[11] = this._validateColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);
|
||||
this.colors.ansi[12] = this._validateColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);
|
||||
this.colors.ansi[13] = this._validateColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
|
||||
this.colors.ansi[14] = this._validateColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
|
||||
this.colors.ansi[15] = this._validateColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
|
||||
}
|
||||
|
||||
private _validateColor(color: string, fallback: string): string {
|
||||
if (!color) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const isColorValid = this._isColorValid(color);
|
||||
|
||||
if (!isColorValid) {
|
||||
console.warn(`Color: ${color} is invalid using fallback ${fallback}`);
|
||||
}
|
||||
|
||||
return isColorValid ? color : fallback;
|
||||
}
|
||||
|
||||
private _isColorValid(color: string): boolean {
|
||||
const litmus = 'red';
|
||||
const d = this._document.createElement('div');
|
||||
d.style.color = litmus;
|
||||
d.style.color = color;
|
||||
|
||||
// Element's style.color will be reverted to litmus or set to '' if an invalid color is given
|
||||
if (color !== litmus && (d.style.color === litmus || d.style.color === '')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
import { GridCache } from './GridCache';
|
||||
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||
import { CHAR_DATA_WIDTH_INDEX } from '../Buffer';
|
||||
import { IColorSet, IRenderDimensions } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { CharData, IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
@@ -26,7 +25,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _state: ICursorState;
|
||||
private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
||||
private _isFocused: boolean;
|
||||
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
|
||||
super(container, 'cursor', zIndex, true, colors);
|
||||
@@ -45,8 +43,8 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||
super.resize(terminal, dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = {
|
||||
x: null,
|
||||
@@ -237,7 +235,7 @@ class CursorBlinkStateManager {
|
||||
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
private renderCallback: () => void
|
||||
private _renderCallback: () => void
|
||||
) {
|
||||
this.isCursorVisible = true;
|
||||
if (terminal.isFocused) {
|
||||
@@ -272,7 +270,7 @@ class CursorBlinkStateManager {
|
||||
this.isCursorVisible = true;
|
||||
if (!this._animationFrame) {
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
}
|
||||
@@ -303,7 +301,7 @@ class CursorBlinkStateManager {
|
||||
// Hide the cursor
|
||||
this.isCursorVisible = false;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
|
||||
@@ -322,7 +320,7 @@ class CursorBlinkStateManager {
|
||||
// Invert visibility and render
|
||||
this.isCursorVisible = !this.isCursorVisible;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
}, BLINK_INTERVAL);
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, IBuffer, ICharMeasure, LinkHoverEventTypes } from '../Types';
|
||||
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
|
||||
import { GridCache } from './GridCache';
|
||||
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
|
||||
import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types';
|
||||
import { IColorSet, IRenderDimensions } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkHoverEvent = null;
|
||||
@@ -18,8 +16,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||
super.resize(terminal, dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = null;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
import { TextRenderLayer } from './TextRenderLayer';
|
||||
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
import { ColorManager } from './ColorManager';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
|
||||
import { ITerminal } from '../Types';
|
||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||
@@ -31,7 +29,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
|
||||
constructor(private _terminal: ITerminal, theme: ITheme) {
|
||||
super();
|
||||
this.colorManager = new ColorManager();
|
||||
this.colorManager = new ColorManager(document);
|
||||
if (theme) {
|
||||
this.colorManager.setTheme(theme);
|
||||
}
|
||||
@@ -84,7 +82,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
// and the terminal needs to refreshed
|
||||
if (this._devicePixelRatio !== devicePixelRatio) {
|
||||
this._devicePixelRatio = devicePixelRatio;
|
||||
this.onResize(this._terminal.cols, this._terminal.rows, true);
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +104,12 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
return this.colorManager.colors;
|
||||
}
|
||||
|
||||
public onResize(cols: number, rows: number, didCharSizeChange: boolean): void {
|
||||
public onResize(cols: number, rows: number): void {
|
||||
// Update character and canvas dimensions
|
||||
this._updateDimensions();
|
||||
|
||||
// Resize all render layers
|
||||
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange));
|
||||
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));
|
||||
|
||||
// Force a refresh
|
||||
if (this._isPaused) {
|
||||
@@ -131,7 +129,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
this.onResize(this._terminal.cols, this._terminal.rows, true);
|
||||
this.onResize(this._terminal.cols, this._terminal.rows);
|
||||
}
|
||||
|
||||
public onBlur(): void {
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBuffer, ICharMeasure, ITerminal } from '../Types';
|
||||
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
|
||||
import { GridCache } from './GridCache';
|
||||
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||
import { ITerminal } from '../Types';
|
||||
import { IColorSet, IRenderDimensions } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
@@ -20,8 +18,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
};
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||
super.resize(terminal, dim);
|
||||
// Resizing the canvas discards the contents of the canvas so clear state
|
||||
this._state = {
|
||||
start: null,
|
||||
|
||||
@@ -5,16 +5,17 @@
|
||||
|
||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';
|
||||
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||
import { CharData, IBuffer, ICharMeasure, ITerminal } from '../Types';
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { GridCache } from './GridCache';
|
||||
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
/**
|
||||
* This CharData looks like a null character, which will forc a clear and render
|
||||
* when the character changes (a regular space ' ' character may not as it's
|
||||
* drawn state is a cleared cell).
|
||||
*/
|
||||
const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
|
||||
// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
|
||||
|
||||
export class TextRenderLayer extends BaseRenderLayer {
|
||||
private _state: GridCache<CharData>;
|
||||
@@ -27,8 +28,8 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
this._state = new GridCache<CharData>();
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions): void {
|
||||
super.resize(terminal, dim);
|
||||
|
||||
// Clear the character width cache if the font or width has changed
|
||||
const terminalFont = this._getFont(terminal, false);
|
||||
@@ -238,13 +239,13 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
* @param x The column of the char.
|
||||
* @param y The row of the char.
|
||||
*/
|
||||
private _clearChar(x: number, y: number): void {
|
||||
let colsToClear = 1;
|
||||
// Clear the adjacent character if it was wide
|
||||
const state = this._state.cache[x][y];
|
||||
if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
|
||||
colsToClear = 2;
|
||||
}
|
||||
this.clearCells(x, y, colsToClear, 1);
|
||||
}
|
||||
// private _clearChar(x: number, y: number): void {
|
||||
// let colsToClear = 1;
|
||||
// // Clear the adjacent character if it was wide
|
||||
// const state = this._state.cache[x][y];
|
||||
// if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
|
||||
// colsToClear = 2;
|
||||
// }
|
||||
// this.clearCells(x, y, colsToClear, 1);
|
||||
// }
|
||||
}
|
||||
|
||||
+5
-10
@@ -5,6 +5,7 @@
|
||||
|
||||
import { ITerminal } from '../Types';
|
||||
import { IEventEmitter, ITheme } from 'xterm';
|
||||
import { IColorSet } from '../shared/Types';
|
||||
|
||||
/**
|
||||
* Flags used to render terminal text properly.
|
||||
@@ -24,7 +25,7 @@ export interface IRenderer extends IEventEmitter {
|
||||
|
||||
setTheme(theme: ITheme): IColorSet;
|
||||
onWindowResize(devicePixelRatio: number): void;
|
||||
onResize(cols: number, rows: number, didCharSizeChange: boolean): void;
|
||||
onResize(cols: number, rows: number): void;
|
||||
onCharSizeChanged(): void;
|
||||
onBlur(): void;
|
||||
onFocus(): void;
|
||||
@@ -39,14 +40,8 @@ export interface IColorManager {
|
||||
colors: IColorSet;
|
||||
}
|
||||
|
||||
export interface IColorSet {
|
||||
foreground: string;
|
||||
background: string;
|
||||
cursor: string;
|
||||
cursorAccent: string;
|
||||
selection: string;
|
||||
ansi: string[];
|
||||
}
|
||||
// TODO: We should probably rewrite the imports for IColorSet, but there's a lot of them
|
||||
export { IColorSet };
|
||||
|
||||
export interface IRenderDimensions {
|
||||
scaledCharWidth: number;
|
||||
@@ -103,7 +98,7 @@ export interface IRenderLayer {
|
||||
/**
|
||||
* Resize the render layer.
|
||||
*/
|
||||
resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void;
|
||||
resize(terminal: ITerminal, dim: IRenderDimensions): void;
|
||||
|
||||
/**
|
||||
* Clear the state of the render layer.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../../Types';
|
||||
import { IColorSet } from '../Types';
|
||||
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||
import { generateCharAtlas } from '../../shared/atlas/CharAtlasGenerator';
|
||||
import { generateConfig, configEquals } from './CharAtlasUtils';
|
||||
|
||||
interface ICharAtlasCacheEntry {
|
||||
bitmap: HTMLCanvasElement | Promise<ImageBitmap>;
|
||||
config: ICharAtlasConfig;
|
||||
ownedBy: ITerminal[];
|
||||
}
|
||||
|
||||
let charAtlasCache: ICharAtlasCacheEntry[] = [];
|
||||
|
||||
/**
|
||||
* Acquires a char atlas, either generating a new one or returning an existing
|
||||
* one that is in use by another terminal.
|
||||
* @param terminal The terminal.
|
||||
* @param colors The colors to use.
|
||||
*/
|
||||
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||
|
||||
// Check to see if the terminal already owns this config
|
||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||
const entry = charAtlasCache[i];
|
||||
const ownedByIndex = entry.ownedBy.indexOf(terminal);
|
||||
if (ownedByIndex >= 0) {
|
||||
if (configEquals(entry.config, newConfig)) {
|
||||
return entry.bitmap;
|
||||
} else {
|
||||
// The configs differ, release the terminal from the entry
|
||||
if (entry.ownedBy.length === 1) {
|
||||
charAtlasCache.splice(i, 1);
|
||||
} else {
|
||||
entry.ownedBy.splice(ownedByIndex, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try match a char atlas from the cache
|
||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||
const entry = charAtlasCache[i];
|
||||
if (configEquals(entry.config, newConfig)) {
|
||||
// Add the terminal to the cache entry and return
|
||||
entry.ownedBy.push(terminal);
|
||||
return entry.bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
const canvasFactory = (width: number, height: number) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
const newEntry: ICharAtlasCacheEntry = {
|
||||
bitmap: generateCharAtlas(window, canvasFactory, newConfig),
|
||||
config: newConfig,
|
||||
ownedBy: [terminal]
|
||||
};
|
||||
charAtlasCache.push(newEntry);
|
||||
return newEntry.bitmap;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../../Types';
|
||||
import { IColorSet } from '../Types';
|
||||
import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||
|
||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
||||
const clonedColors = {
|
||||
foreground: colors.foreground,
|
||||
background: colors.background,
|
||||
cursor: null,
|
||||
cursorAccent: null,
|
||||
selection: null,
|
||||
ansi: colors.ansi.slice(0, 16)
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
allowTransparency: terminal.options.allowTransparency,
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
|
||||
export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
||||
for (let i = 0; i < a.colors.ansi.length; i++) {
|
||||
if (a.colors.ansi[i] !== b.colors.ansi[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return a.devicePixelRatio === b.devicePixelRatio &&
|
||||
a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
a.fontWeightBold === b.fontWeightBold &&
|
||||
a.allowTransparency === b.allowTransparency &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const INVERTED_DEFAULT_COLOR = -1;
|
||||
export const DIM_OPACITY = 0.5;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export interface IColorSet {
|
||||
foreground: string;
|
||||
background: string;
|
||||
cursor: string;
|
||||
cursorAccent: string;
|
||||
selection: string;
|
||||
ansi: string[];
|
||||
}
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
import { FontWeight } from 'xterm';
|
||||
import { isFirefox } from './utils/Browser';
|
||||
import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types';
|
||||
import { isFirefox } from '../utils/Browser';
|
||||
|
||||
declare const Promise: any;
|
||||
|
||||
@@ -15,43 +16,27 @@ export interface IOffscreenCanvas {
|
||||
transferToImageBitmap(): ImageBitmap;
|
||||
}
|
||||
|
||||
export interface ICharAtlasRequest {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
fontWeightBold: FontWeight;
|
||||
background: string;
|
||||
foreground: string;
|
||||
ansiColors: string[];
|
||||
devicePixelRatio: number;
|
||||
allowTransparency: boolean;
|
||||
}
|
||||
|
||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
|
||||
/**
|
||||
* Generates a char atlas.
|
||||
* @param context The window or worker context.
|
||||
* @param canvasFactory A function to generate a canvas with a width or height.
|
||||
* @param request The config for the new char atlas.
|
||||
*/
|
||||
export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, request: ICharAtlasRequest): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const cellWidth = request.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const cellHeight = request.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
const canvas = canvasFactory(
|
||||
/*255 ascii chars*/255 * cellWidth,
|
||||
(/*default+default bold*/2 + /*0-15*/16) * cellHeight
|
||||
);
|
||||
const ctx = canvas.getContext('2d', {alpha: request.allowTransparency});
|
||||
const ctx = canvas.getContext('2d', {alpha: config.allowTransparency});
|
||||
|
||||
ctx.fillStyle = request.background;
|
||||
ctx.fillStyle = config.colors.background;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = request.foreground;
|
||||
ctx.font = getFont(request.fontWeight, request);
|
||||
ctx.fillStyle = config.colors.foreground;
|
||||
ctx.font = getFont(config.fontWeight, config);
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// Default color
|
||||
@@ -65,7 +50,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
|
||||
}
|
||||
// Default color bold
|
||||
ctx.save();
|
||||
ctx.font = getFont(request.fontWeightBold, request);
|
||||
ctx.font = getFont(config.fontWeightBold, config);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
@@ -77,11 +62,11 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
|
||||
ctx.restore();
|
||||
|
||||
// Colors 0-15
|
||||
ctx.font = getFont(request.fontWeight, request);
|
||||
ctx.font = getFont(config.fontWeight, config);
|
||||
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
|
||||
// colors 8-15 are bold
|
||||
if (colorIndex === 8) {
|
||||
ctx.font = getFont(request.fontWeightBold, request);
|
||||
ctx.font = getFont(config.fontWeightBold, config);
|
||||
}
|
||||
const y = (colorIndex + 2) * cellHeight;
|
||||
// Draw ascii characters
|
||||
@@ -90,7 +75,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = request.ansiColors[colorIndex];
|
||||
ctx.fillStyle = config.colors.ansi[colorIndex];
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -115,9 +100,9 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
|
||||
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Remove the background color from the image so characters may overlap
|
||||
const r = parseInt(request.background.substr(1, 2), 16);
|
||||
const g = parseInt(request.background.substr(3, 2), 16);
|
||||
const b = parseInt(request.background.substr(5, 2), 16);
|
||||
const r = parseInt(config.colors.background.substr(1, 2), 16);
|
||||
const g = parseInt(config.colors.background.substr(3, 2), 16);
|
||||
const b = parseInt(config.colors.background.substr(5, 2), 16);
|
||||
clearColor(charAtlasImageData, r, g, b);
|
||||
|
||||
return context.createImageBitmap(charAtlasImageData);
|
||||
@@ -136,6 +121,6 @@ function clearColor(imageData: ImageData, r: number, g: number, b: number): void
|
||||
}
|
||||
}
|
||||
|
||||
function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string {
|
||||
return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
|
||||
function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string {
|
||||
return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { FontWeight } from 'xterm';
|
||||
import { IColorSet } from '../Types';
|
||||
|
||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
|
||||
export interface ICharAtlasConfig {
|
||||
devicePixelRatio: number;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
fontWeightBold: FontWeight;
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
allowTransparency: boolean;
|
||||
colors: IColorSet;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { ICharMeasure, ITerminal } from '../Types';
|
||||
import { ICharMeasure } from '../Types';
|
||||
import { assert } from 'chai';
|
||||
import { CharMeasure } from './CharMeasure';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
|
||||
import { ICharMeasure, ITerminalOptions } from '../Types';
|
||||
import { EventEmitter } from '../EventEmitter';
|
||||
|
||||
/**
|
||||
@@ -23,10 +23,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
this._document = document;
|
||||
this._parentElement = parentElement;
|
||||
this._measureElement = this._document.createElement('span');
|
||||
this._measureElement.style.position = 'absolute';
|
||||
this._measureElement.style.top = '0';
|
||||
this._measureElement.style.left = '-9999em';
|
||||
this._measureElement.style.lineHeight = 'normal';
|
||||
this._measureElement.classList.add('xterm-char-measure-element');
|
||||
this._measureElement.textContent = 'W';
|
||||
this._measureElement.setAttribute('aria-hidden', 'true');
|
||||
this._parentElement.appendChild(this._measureElement);
|
||||
@@ -41,7 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
}
|
||||
|
||||
public measure(options: ITerminalOptions): void {
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
||||
const geometry = this._measureElement.getBoundingClientRect();
|
||||
// The element is likely currently display:none, we should retain the
|
||||
@@ -55,5 +52,4 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
this.emit('charsizechanged');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
import { assert } from 'chai';
|
||||
import { CircularList } from './CircularList';
|
||||
|
||||
class TestCircularList<T> extends CircularList<T> {
|
||||
public get array(): T[] { return this._array; }
|
||||
}
|
||||
|
||||
describe('CircularList', () => {
|
||||
describe('push', () => {
|
||||
it('should push values onto the array', () => {
|
||||
|
||||
@@ -269,6 +269,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
tabSet(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockBuffer implements IBuffer {
|
||||
@@ -276,6 +279,7 @@ export class MockBuffer implements IBuffer {
|
||||
lines: ICircularList<[number, string, number, number][]>;
|
||||
ydisp: number;
|
||||
ybase: number;
|
||||
hasScrollback: boolean;
|
||||
y: number;
|
||||
x: number;
|
||||
tabs: any;
|
||||
@@ -310,7 +314,7 @@ export class MockRenderer implements IRenderer {
|
||||
}
|
||||
dimensions: IRenderDimensions;
|
||||
setTheme(theme: ITheme): IColorSet { return <IColorSet>{}; }
|
||||
onResize(cols: number, rows: number, didCharSizeChange: boolean): void {}
|
||||
onResize(cols: number, rows: number): void {}
|
||||
onCharSizeChanged(): void {}
|
||||
onBlur(): void {}
|
||||
onFocus(): void {}
|
||||
@@ -337,6 +341,9 @@ export class MockViewport implements IViewport {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
syncScrollArea(): void { }
|
||||
getLinesScrolled(ev: WheelEvent): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockCompositionHelper implements ICompositionHelper {
|
||||
|
||||
+7
-5
@@ -117,11 +117,13 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm .xterm-char-measure-element {
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
@@ -144,10 +146,6 @@
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] {
|
||||
outline: 1px solid #F80;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
@@ -155,3 +153,7 @@
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user