Merge branch 'master' into unused_func

This commit is contained in:
Daniel Imms
2018-03-20 19:00:24 -07:00
committed by GitHub
55 changed files with 824 additions and 733 deletions
+2
View File
@@ -32,6 +32,7 @@ Christopher Jeffrey <chjjeffrey@gmail.com>
coderaiser <mnemonic.enemy@gmail.com>
Damien Tournoud <damien@platform.sh>
Dan Brown <jdanbrown@gmail.com>
Daniel Griffen <dagriffe@microsoft.com>
Daniel Griffen <daniel@griffen.io>
Daniel Imms <daimms@microsoft.com>
Daniel Risacher <drisacher@gmail.com>
@@ -74,6 +75,7 @@ Martin Chloride <i@martincl2.me>
Martin Koppehel <martin.koppehel@st.ovgu.de>
Martin Wang <jiahaow@ca.ibm.com>
Matt Bierner <matb@microsoft.com>
Matthew James <matthew.d.james87@gmail.com>
Michael Irwin <mikesir87@gmail.com>
Mikko Karvonen <mikko.karvonen@arm.com>
mofux <t.zilz@mofux.org>
+4
View File
@@ -50,3 +50,7 @@ By contributing code to xterm.js you
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
agreement with you.)
### Third party dependencies
We prefer to not include any non-dev third party dependencies in order to keep our code minimal, performant and secure. If you plan on adding a dependency on a third party library it's a good idea to discuss the need in an issue with the maintainers first.
+1 -1
View File
@@ -16,4 +16,4 @@ RUN npm install
COPY . /usr/src/app
# Run the tests and build, to make sure everything is working nicely
RUN npm run build && npm run test
RUN npm run build && npm run webpack && npm run test
+14 -1
View File
@@ -127,6 +127,7 @@ computational environment for Jupyter, supporting interactive data science and s
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
@@ -144,7 +145,19 @@ Then open your project's [Public URL](https://help.sourcelair.com/projects/the-p
### Docker
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). To run the demo and builder in parallel, run the following command in your terminal:
First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer).
Xterm.js [provides a pre-built Docker image](https://hub.docker.com/r/xtermjs/xterm.js/) to help run the demo easily (Git tags are built as [tagged Docker images](https://hub.docker.com/r/xtermjs/xterm.js/tags/) too).
To run the just demo (with no editing access). run the following command in your terminal:
```
docker run -p 3000:3000 xtermjs/xterm.js
```
Then open http://0.0.0.0:3000 in a web browser to access the demo.
To run the demo and builder in parallel, run the following command in your terminal:
```
docker-compose up
+3
View File
@@ -3,6 +3,7 @@ import * as attach from '../build/addons/attach/attach';
import * as fit from '../build/addons/fit/fit';
import * as fullscreen from '../build/addons/fullscreen/fullscreen';
import * as search from '../build/addons/search/search';
import * as webLinks from '../build/addons/webLinks/webLinks';
import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat';
@@ -10,6 +11,7 @@ Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
Terminal.applyAddon(webLinks);
Terminal.applyAddon(winptyCompat);
@@ -121,6 +123,7 @@ function createTerminal() {
term.open(terminalContainer);
term.winptyCompatInit();
term.webLinksInit();
term.fit();
term.focus();
+1
View File
@@ -2,6 +2,7 @@ version: "3"
services:
web:
image: xtermjs/xterm.js:latest
build: .
volumes:
- ./:/usr/src/app
+1 -1
View File
@@ -23,7 +23,7 @@ const tsProject = ts.createProject('tsconfig.json');
const srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
const addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem'];
const addons = fs.readdirSync(`${__dirname}/src/addons`);
// Under some environments like TravisCI, this comes out at absolute which can
// break the build. This ensures that the outDir is absolute.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm",
"version": "3.1.0-master",
"version": "3.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "3.1.0",
"version": "3.2.0",
"ignore": [
"demo",
"test",
+9 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -11
View File
@@ -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;
+6 -2
View File
@@ -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;
-3
View File
@@ -18,9 +18,6 @@ class TestSelectionModel extends SelectionModel {
}
describe('SelectionManager', () => {
let window: Window;
let document: Document;
let terminal: ITerminal;
let model: TestSelectionModel;
+3 -3
View File
@@ -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(',');
+1 -1
View File
@@ -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;

Some files were not shown because too many files have changed in this diff Show More