Merge remote-tracking branch 'upstream/master' into 361_circular_list_scrollback
This commit is contained in:
@@ -1,213 +0,0 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
|
||||
* events, displaying the in-progress composition to the UI and forwarding the final composition
|
||||
* to the handler.
|
||||
* @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
|
||||
* @param {HTMLElement} compositionView The element to display the in-progress composition in.
|
||||
* @param {Terminal} terminal The Terminal to forward the finished composition to.
|
||||
*/
|
||||
function CompositionHelper(textarea, compositionView, terminal) {
|
||||
this.textarea = textarea;
|
||||
this.compositionView = compositionView;
|
||||
this.terminal = terminal;
|
||||
|
||||
// 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.
|
||||
this.isComposing = false;
|
||||
|
||||
// The input currently being composed, eg. via a mobile keyboard, speech input or IME.
|
||||
this.compositionText = null;
|
||||
|
||||
// The position within the input textarea's value of the current composition.
|
||||
this.compositionPosition = { start: null, end: null };
|
||||
|
||||
// Whether a composition is in the process of being sent, setting this to false will cancel
|
||||
// any in-progress composition.
|
||||
this.isSendingComposition = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionstart event, activating the composition view.
|
||||
*/
|
||||
CompositionHelper.prototype.compositionstart = function() {
|
||||
this.isComposing = true;
|
||||
this.compositionPosition.start = this.textarea.value.length;
|
||||
this.compositionView.textContent = '';
|
||||
this.compositionView.classList.add('active');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the compositionupdate event, updating the composition view.
|
||||
* @param {CompositionEvent} ev The event.
|
||||
*/
|
||||
CompositionHelper.prototype.compositionupdate = function(ev) {
|
||||
this.compositionView.textContent = ev.data;
|
||||
this.updateCompositionElements();
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.compositionPosition.end = self.textarea.value.length;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the compositionend event, hiding the composition view and sending the composition to
|
||||
* the handler.
|
||||
*/
|
||||
CompositionHelper.prototype.compositionend = function() {
|
||||
this.finalizeComposition(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
|
||||
* @return Whether the Terminal should continue processing the keydown event.
|
||||
*/
|
||||
CompositionHelper.prototype.keydown = function(ev) {
|
||||
if (this.isComposing || this.isSendingComposition) {
|
||||
if (ev.keyCode === 229) {
|
||||
// Continue composing if the keyCode is the "composition character"
|
||||
return false;
|
||||
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
||||
// Continue composing if the keyCode is a modifier key
|
||||
return false;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finalizes the composition, resuming regular input actions. This is called when a composition
|
||||
* is ending.
|
||||
* @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
|
||||
* the input. This should be false if a non-composition keystroke is entered before the
|
||||
* compositionend event is triggered, such as enter, so that the composition is send before
|
||||
* the command is executed.
|
||||
*/
|
||||
CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
|
||||
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;
|
||||
var 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.
|
||||
var currentCompositionPosition = {
|
||||
start: this.compositionPosition.start,
|
||||
end: this.compositionPosition.end,
|
||||
}
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
// complete. This ensures the correct character is retrieved, this solution was used
|
||||
// because:
|
||||
// - The compositionend event's data property is unreliable, at least on Chromium
|
||||
// - 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.
|
||||
var self = this;
|
||||
this.isSendingComposition = true;
|
||||
setTimeout(function () {
|
||||
// Ensure that the input has not already been sent
|
||||
if (self.isSendingComposition) {
|
||||
self.isSendingComposition = false;
|
||||
var input;
|
||||
if (self.isComposing) {
|
||||
// Use the end position to get the string if a new composition has started.
|
||||
input = self.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 = self.textarea.value.substring(currentCompositionPosition.start);
|
||||
}
|
||||
self.terminal.handler(input);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply any changes made to the textarea after the current event chain is allowed to complete.
|
||||
* This should be called when not currently composing but a keydown event with the "composition
|
||||
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
||||
* IME is active.
|
||||
*/
|
||||
CompositionHelper.prototype.handleAnyTextareaChanges = function() {
|
||||
var oldValue = this.textarea.value;
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
// Ignore if a composition has started since the timeout
|
||||
if (!self.isComposing) {
|
||||
var newValue = self.textarea.value;
|
||||
var diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
self.terminal.handler(diff);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Positions the composition view on top of the cursor and the textarea just below it (so the
|
||||
* IME helper dialog is positioned correctly).
|
||||
*/
|
||||
CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
|
||||
if (!this.isComposing) {
|
||||
return;
|
||||
}
|
||||
var cursor = this.terminal.element.querySelector('.terminal-cursor');
|
||||
if (cursor) {
|
||||
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
|
||||
// the .xterm element.
|
||||
var xtermRows = this.terminal.element.querySelector('.xterm-rows');
|
||||
var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
|
||||
|
||||
this.compositionView.style.left = cursor.offsetLeft + 'px';
|
||||
this.compositionView.style.top = cursorTop + 'px';
|
||||
this.compositionView.style.height = cursor.offsetHeight + 'px';
|
||||
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
var compositionViewBounds = this.compositionView.getBoundingClientRect();
|
||||
this.textarea.style.left = cursor.offsetLeft + '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) {
|
||||
setTimeout(this.updateCompositionElements.bind(this, true), 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
* @private
|
||||
*/
|
||||
CompositionHelper.prototype.clearTextareaPosition = function() {
|
||||
this.textarea.style.left = '';
|
||||
this.textarea.style.top = '';
|
||||
};
|
||||
|
||||
export { CompositionHelper };
|
||||
@@ -1,20 +1,20 @@
|
||||
var assert = require('chai').assert;
|
||||
var Terminal = require('../xterm');
|
||||
import { assert } from 'chai';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
|
||||
describe('CompositionHelper', function () {
|
||||
describe('CompositionHelper', () => {
|
||||
var terminal;
|
||||
var compositionHelper;
|
||||
var compositionView;
|
||||
var textarea;
|
||||
var handledText;
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(() => {
|
||||
compositionView = {
|
||||
classList: {
|
||||
add: function () {},
|
||||
remove: function () {},
|
||||
add: () => {},
|
||||
remove: () => {},
|
||||
},
|
||||
getBoundingClientRect: function () {
|
||||
getBoundingClientRect: () => {
|
||||
return { width: 0 };
|
||||
},
|
||||
style: {
|
||||
@@ -32,7 +32,7 @@ describe('CompositionHelper', function () {
|
||||
};
|
||||
terminal = {
|
||||
element: {
|
||||
querySelector: function () {
|
||||
querySelector: () => {
|
||||
return { offsetLeft: 0, offsetTop: 0 };
|
||||
}
|
||||
},
|
||||
@@ -41,53 +41,26 @@ describe('CompositionHelper', function () {
|
||||
}
|
||||
};
|
||||
handledText = '';
|
||||
compositionHelper = new Terminal.CompositionHelper(textarea, compositionView, terminal);
|
||||
compositionHelper = new CompositionHelper(textarea, compositionView, terminal);
|
||||
});
|
||||
|
||||
describe('Public API', function () {
|
||||
it('should define CompositionHelper.prototype.compositionstart', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.compositionstart);
|
||||
});
|
||||
it('should define CompositionHelper.prototype.compositionupdate', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.compositionupdate);
|
||||
});
|
||||
it('should define CompositionHelper.prototype.compositionend', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.compositionend);
|
||||
});
|
||||
it('should define CompositionHelper.prototype.finalizeComposition', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.finalizeComposition);
|
||||
});
|
||||
it('should define CompositionHelper.prototype.handleAnyTextareaChanges', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.handleAnyTextareaChanges);
|
||||
});
|
||||
it('should define CompositionHelper.prototype.updateCompositionElements', function () {
|
||||
assert.isDefined(Terminal.CompositionHelper.prototype.updateCompositionElements);
|
||||
});
|
||||
it('should define CompositionHelper.isComposing', function () {
|
||||
assert.isDefined(compositionHelper.isComposing);
|
||||
});
|
||||
it('should define CompositionHelper.isSendingComposition', function () {
|
||||
assert.isDefined(compositionHelper.isSendingComposition);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input', function () {
|
||||
describe('Input', () => {
|
||||
it('Should insert simple characters', function (done) {
|
||||
// First character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇ');
|
||||
// Second character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇㅇ');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -101,29 +74,29 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '앙');
|
||||
// Second character '앙'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = '앙ㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '앙아';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙앙';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '앙앙');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -141,21 +114,21 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Start second character '아' in first character
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아아'
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '아아');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -170,16 +143,16 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'あ'
|
||||
compositionHelper.compositionupdate({ data: 'だあ' });
|
||||
textarea.value = 'だあ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'だあ');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -193,20 +166,20 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ー'
|
||||
compositionHelper.compositionupdate({ data: 'だー' });
|
||||
textarea.value = 'だー';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to katakana 'ダー'
|
||||
compositionHelper.compositionupdate({ data: 'ダー' });
|
||||
textarea.value = 'ダー';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ダー');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -221,20 +194,20 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'い' });
|
||||
textarea.value = 'い';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ま'
|
||||
compositionHelper.compositionupdate({ data: 'いm' });
|
||||
textarea.value = 'いm';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'いま' });
|
||||
textarea.value = 'いま';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to kanji '今'
|
||||
compositionHelper.compositionupdate({ data: '今' });
|
||||
textarea.value = '今';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '今');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -249,11 +222,11 @@ describe('CompositionHelper', function () {
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
// Second character '1' (a non-composition character)
|
||||
textarea.value = 'ㅇ1';
|
||||
setTimeout(function() { // wait for any textarea updates
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇ1');
|
||||
done();
|
||||
}, 0);
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
|
||||
interface IPosition {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
|
||||
* events, displaying the in-progress composition to the UI and forwarding the final composition
|
||||
* to the handler.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* The position within the input textarea's value of the current composition.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
constructor(
|
||||
private textarea: HTMLTextAreaElement,
|
||||
private compositionView: HTMLElement,
|
||||
private terminal: ITerminal
|
||||
) {
|
||||
this.isComposing = false;
|
||||
this.isSendingComposition = false;
|
||||
this.compositionPosition = { start: null, end: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionstart event, activating the composition view.
|
||||
*/
|
||||
public compositionstart() {
|
||||
this.isComposing = true;
|
||||
this.compositionPosition.start = this.textarea.value.length;
|
||||
this.compositionView.textContent = '';
|
||||
this.compositionView.classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionupdate event, updating the composition view.
|
||||
* @param {CompositionEvent} ev The event.
|
||||
*/
|
||||
public compositionupdate(ev: CompositionEvent) {
|
||||
this.compositionView.textContent = ev.data;
|
||||
this.updateCompositionElements();
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.compositionPosition.end = self.textarea.value.length;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionend event, hiding the composition view and sending the composition to
|
||||
* the handler.
|
||||
*/
|
||||
public compositionend() {
|
||||
this.finalizeComposition(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
|
||||
* @param ev The keydown event.
|
||||
* @return Whether the Terminal should continue processing the keydown event.
|
||||
*/
|
||||
public keydown(ev: KeyboardEvent) {
|
||||
if (this.isComposing || this.isSendingComposition) {
|
||||
if (ev.keyCode === 229) {
|
||||
// Continue composing if the keyCode is the "composition character"
|
||||
return false;
|
||||
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
||||
// Continue composing if the keyCode is a modifier key
|
||||
return false;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the composition, resuming regular input actions. This is called when a composition
|
||||
* is ending.
|
||||
* @param waitForPropogation Whether to wait for events to propogate before sending
|
||||
* the input. This should be false if a non-composition keystroke is entered before the
|
||||
* compositionend event is triggered, such as enter, so that the composition is send before
|
||||
* the command is executed.
|
||||
*/
|
||||
private finalizeComposition(waitForPropogation: boolean) {
|
||||
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;
|
||||
var 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.
|
||||
var currentCompositionPosition = {
|
||||
start: this.compositionPosition.start,
|
||||
end: this.compositionPosition.end,
|
||||
}
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
// complete. This ensures the correct character is retrieved, this solution was used
|
||||
// because:
|
||||
// - The compositionend event's data property is unreliable, at least on Chromium
|
||||
// - 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.
|
||||
var self = this;
|
||||
this.isSendingComposition = true;
|
||||
setTimeout(function () {
|
||||
// Ensure that the input has not already been sent
|
||||
if (self.isSendingComposition) {
|
||||
self.isSendingComposition = false;
|
||||
var input;
|
||||
if (self.isComposing) {
|
||||
// Use the end position to get the string if a new composition has started.
|
||||
input = self.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 = self.textarea.value.substring(currentCompositionPosition.start);
|
||||
}
|
||||
self.terminal.handler(input);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any changes made to the textarea after the current event chain is allowed to complete.
|
||||
* This should be called when not currently composing but a keydown event with the "composition
|
||||
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
||||
* IME is active.
|
||||
*/
|
||||
private handleAnyTextareaChanges() {
|
||||
var oldValue = this.textarea.value;
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
// Ignore if a composition has started since the timeout
|
||||
if (!self.isComposing) {
|
||||
var newValue = self.textarea.value;
|
||||
var diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
self.terminal.handler(diff);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the composition view on top of the cursor and the textarea just below it (so the
|
||||
* IME helper dialog is positioned correctly).
|
||||
* @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
|
||||
* necessary as the IME events across browsers are not consistently triggered.
|
||||
*/
|
||||
public updateCompositionElements(dontRecurse?: boolean) {
|
||||
if (!this.isComposing) {
|
||||
return;
|
||||
}
|
||||
var cursor = <HTMLElement>this.terminal.element.querySelector('.terminal-cursor');
|
||||
if (cursor) {
|
||||
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
|
||||
// the .xterm element.
|
||||
var xtermRows = <HTMLElement>this.terminal.element.querySelector('.xterm-rows');
|
||||
var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
|
||||
|
||||
this.compositionView.style.left = cursor.offsetLeft + 'px';
|
||||
this.compositionView.style.top = cursorTop + 'px';
|
||||
this.compositionView.style.height = cursor.offsetHeight + 'px';
|
||||
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
var compositionViewBounds = this.compositionView.getBoundingClientRect();
|
||||
this.textarea.style.left = cursor.offsetLeft + '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) {
|
||||
setTimeout(this.updateCompositionElements.bind(this, true), 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
* @private
|
||||
*/
|
||||
private clearTextareaPosition() {
|
||||
this.textarea.style.left = '';
|
||||
this.textarea.style.top = '';
|
||||
};
|
||||
}
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
function EventEmitter() {
|
||||
|
||||
+22
-2
@@ -1,14 +1,34 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export interface IBrowser {
|
||||
isNode: boolean;
|
||||
userAgent: string;
|
||||
platform: string;
|
||||
isFirefox: boolean;
|
||||
isMSIE: boolean;
|
||||
isMac: boolean;
|
||||
isIpad: boolean;
|
||||
isIphone: boolean;
|
||||
isMSWindows: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminal {
|
||||
element: HTMLElement;
|
||||
rowContainer: HTMLElement;
|
||||
textarea: HTMLTextAreaElement;
|
||||
ydisp: number;
|
||||
lines: string[];
|
||||
rows: number;
|
||||
browser: IBrowser;
|
||||
|
||||
/**
|
||||
* Emit the 'data' event and populate the given data.
|
||||
* @param data The data to populate in the event.
|
||||
*/
|
||||
handler(data: string);
|
||||
on(event: string, callback: () => void);
|
||||
scrollDisp(disp: number, suppressScrollEvent: boolean);
|
||||
cancel(ev: Event, force?: boolean);
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
@@ -8,15 +7,19 @@ import { ITerminal } from './Interfaces';
|
||||
/**
|
||||
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
|
||||
* Logic for the virtual scroll bar is included in this object.
|
||||
* @param viewportElement The DOM element acting as the viewport.
|
||||
* @param scrollArea The DOM element acting as the scroll area.
|
||||
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
|
||||
*/
|
||||
export class Viewport {
|
||||
private currentRowHeight: number;
|
||||
private lastRecordedBufferLength: number;
|
||||
private lastRecordedViewportHeight: number;
|
||||
|
||||
/**
|
||||
* 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 charMeasureElement A DOM element used to measure the character size of. the terminal.
|
||||
*/
|
||||
constructor(
|
||||
private terminal: ITerminal,
|
||||
private viewportElement: HTMLElement,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/*
|
||||
* Implements the attach method, that
|
||||
* attaches the terminal to a WebSocket stream.
|
||||
*
|
||||
* The bidirectional argument indicates, whether the terminal should
|
||||
* send data to the socket as well and is true, by default.
|
||||
/**
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
* @module xterm/addons/attach/attach
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
@@ -26,12 +24,6 @@
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a WebSocket
|
||||
* stream.
|
||||
*
|
||||
* @module xterm/addons/attach/attach
|
||||
*/
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
|
||||
+12
-17
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Fit terminal columns and rows to the dimensions of its
|
||||
* DOM element.
|
||||
/**
|
||||
* Fit terminal columns and rows to the dimensions of its DOM element.
|
||||
*
|
||||
* Approach:
|
||||
* - Rows: Truncate the division of the terminal parent element height
|
||||
* by the terminal row height
|
||||
* ## Approach
|
||||
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
|
||||
*
|
||||
* - Columns: Truncate the division of the terminal parent element width by
|
||||
* the terminal character width (apply display: inline at the
|
||||
* terminal row and truncate its width with the current number
|
||||
* of columns)
|
||||
* - Columns: Truncate the division of the terminal parent element width by the terminal character
|
||||
* width (apply display: inline at the terminal row and truncate its width with the current
|
||||
* number of columns).
|
||||
* @module xterm/addons/fit/fit
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (fit) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
@@ -29,17 +29,12 @@
|
||||
fit(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
/**
|
||||
* This module provides methods for fitting a terminal's size to a parent container.
|
||||
*
|
||||
* @module xterm/addons/fit/fit
|
||||
*/
|
||||
var exports = {};
|
||||
|
||||
exports.proposeGeometry = function (term) {
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
|
||||
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
|
||||
parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')),
|
||||
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
|
||||
elementStyle = window.getComputedStyle(term.element),
|
||||
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
|
||||
@@ -62,7 +57,7 @@
|
||||
subjectRow.innerHTML = contentBuffer;
|
||||
|
||||
rows = parseInt(availableHeight / characterHeight);
|
||||
cols = parseInt(availableWidth / characterWidth) - 1;
|
||||
cols = parseInt(availableWidth / characterWidth);
|
||||
|
||||
geometry = {cols: cols, rows: rows};
|
||||
return geometry;
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
/*
|
||||
/**
|
||||
* Fullscreen addon for xterm.js
|
||||
*
|
||||
* Implements the toggleFullscreen function.
|
||||
*
|
||||
* If the `fullscreen` argument has been supplied, then
|
||||
* if it is true, the fullscreen mode gets turned on,
|
||||
* if it is false or null, the fullscreen mode gets turned off.
|
||||
*
|
||||
* If the `fullscreen` argument has not been supplied, the
|
||||
* fullscreen mode is being toggled.
|
||||
* @module xterm/addons/fullscreen/fullscreen
|
||||
* @license MIT
|
||||
*/
|
||||
(function (fullscreen) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
@@ -30,6 +23,11 @@
|
||||
})(function (Xterm) {
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Toggle the given terminal's fullscreen mode.
|
||||
* @param {Xterm} term - The terminal to toggle full screen mode
|
||||
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
|
||||
*/
|
||||
exports.toggleFullScreen = function (term, fullscreen) {
|
||||
var fn;
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
|
||||
* @module xterm/addons/linkify/linkify
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (linkify) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
@@ -18,12 +24,6 @@
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module provides methods for convertings valid URL substrings
|
||||
* into HTML anchor elements (links), inside a terminal view.
|
||||
*
|
||||
* @module xterm/addons/linkify/linkify
|
||||
*/
|
||||
var exports = {},
|
||||
protocolClause = '(https?:\\/\\/)',
|
||||
domainCharacterSet = '[\\da-z\\.-]+',
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/*
|
||||
* Implements the attach method that
|
||||
* attaches the terminal to a Terminado WebSocket stream.
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
|
||||
*
|
||||
* The bidirectional argument indicates, whether the terminal should
|
||||
* send data to the socket as well and is true, by default.
|
||||
* @module xterm/addons/terminado/terminado
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
@@ -26,12 +25,6 @@
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a WebSocket
|
||||
* stream.
|
||||
*
|
||||
* @module xterm/addons/attach/attach
|
||||
*/
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var assert = require('chai').assert;
|
||||
var Terminal = require('../xterm');
|
||||
var Clipboard = require('../handlers/Clipboard');
|
||||
import { assert } from 'chai';
|
||||
import * as Terminal from '../xterm';
|
||||
import * as Clipboard from './Clipboard';
|
||||
|
||||
|
||||
describe('evaluateCopiedTextProcessing', function () {
|
||||
@@ -1,13 +1,27 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||
* Clipboard handler module: exports methods for handling all clipboard-related events in the
|
||||
* terminal.
|
||||
* @module xterm/handlers/Clipboard
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clipboard handler module. This module contains methods for handling all
|
||||
* clipboard-related events appropriately in the terminal.
|
||||
* @module xterm/handlers/Clipboard
|
||||
*/
|
||||
import { ITerminal } from '../Interfaces';
|
||||
|
||||
// We are extending the ClipboardEvent interface, since TypeScript has not declared the
|
||||
// clientX and clientY properties that we use.
|
||||
interface IClipboardEvent extends ClipboardEvent {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
interface IWindow extends Window {
|
||||
clipboardData?: {
|
||||
getData(format: string): string;
|
||||
setData(format: string, data: string);
|
||||
};
|
||||
}
|
||||
|
||||
declare var window: IWindow;
|
||||
|
||||
/**
|
||||
* Prepares text copied from terminal selection, to be saved in the clipboard by:
|
||||
@@ -16,14 +30,14 @@
|
||||
* @param {string} text The copied text that needs processing for storing in clipboard
|
||||
* @returns {string}
|
||||
*/
|
||||
function prepareTextForClipboard(text) {
|
||||
var space = String.fromCharCode(32),
|
||||
export function prepareTextForClipboard(text: string): string {
|
||||
let space = String.fromCharCode(32),
|
||||
nonBreakingSpace = String.fromCharCode(160),
|
||||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
|
||||
processedText = text.split('\n').map(function (line) {
|
||||
// Strip all trailing white spaces and convert all non-breaking spaces
|
||||
// to regular spaces.
|
||||
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
|
||||
let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
|
||||
|
||||
return processedLine;
|
||||
}).join('\n');
|
||||
@@ -35,8 +49,10 @@ function prepareTextForClipboard(text) {
|
||||
* Binds copy functionality to the given terminal.
|
||||
* @param {ClipboardEvent} ev The original copy event to be handled
|
||||
*/
|
||||
function copyHandler(ev, term) {
|
||||
var copiedText = window.getSelection().toString(),
|
||||
export function copyHandler(ev: IClipboardEvent, term: ITerminal) {
|
||||
// We cast `window` to `any` type, because TypeScript has not declared the `clipboardData`
|
||||
// property that we use below for Internet Explorer.
|
||||
let copiedText = window.getSelection().toString(),
|
||||
text = prepareTextForClipboard(copiedText);
|
||||
|
||||
if (term.browser.isMSIE) {
|
||||
@@ -53,10 +69,12 @@ function copyHandler(ev, term) {
|
||||
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
function pasteHandler(ev, term) {
|
||||
export function pasteHandler(ev: IClipboardEvent, term: ITerminal) {
|
||||
ev.stopPropagation();
|
||||
|
||||
var dispatchPaste = function(text) {
|
||||
let text: string;
|
||||
|
||||
let dispatchPaste = function(text) {
|
||||
term.handler(text);
|
||||
term.textarea.value = '';
|
||||
return term.cancel(ev);
|
||||
@@ -64,12 +82,12 @@ function pasteHandler(ev, term) {
|
||||
|
||||
if (term.browser.isMSIE) {
|
||||
if (window.clipboardData) {
|
||||
var text = window.clipboardData.getData('Text');
|
||||
text = window.clipboardData.getData('Text');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
} else {
|
||||
if (ev.clipboardData) {
|
||||
var text = ev.clipboardData.getData('text/plain');
|
||||
text = ev.clipboardData.getData('text/plain');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
}
|
||||
@@ -88,16 +106,16 @@ function pasteHandler(ev, term) {
|
||||
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
function rightClickHandler(ev, term) {
|
||||
var s = document.getSelection(),
|
||||
export function rightClickHandler(ev: IClipboardEvent, term: ITerminal) {
|
||||
let s = document.getSelection(),
|
||||
selectedText = prepareTextForClipboard(s.toString()),
|
||||
clickIsOnSelection = false;
|
||||
clickIsOnSelection = false,
|
||||
x = ev.clientX,
|
||||
y = ev.clientY;
|
||||
|
||||
if (s.rangeCount) {
|
||||
var r = s.getRangeAt(0),
|
||||
let r = s.getRangeAt(0),
|
||||
cr = r.getClientRects(),
|
||||
x = ev.clientX,
|
||||
y = ev.clientY,
|
||||
i, rect;
|
||||
|
||||
for (i=0; i<cr.length; i++) {
|
||||
@@ -127,7 +145,7 @@ function rightClickHandler(ev, term) {
|
||||
term.textarea.style.height = '20px';
|
||||
term.textarea.style.left = (x - 10) + 'px';
|
||||
term.textarea.style.top = (y - 10) + 'px';
|
||||
term.textarea.style.zIndex = 1000;
|
||||
term.textarea.style.zIndex = '1000';
|
||||
term.textarea.focus();
|
||||
|
||||
// Reset the terminal textarea's styling
|
||||
@@ -141,7 +159,3 @@ function rightClickHandler(ev, term) {
|
||||
}, 4);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler
|
||||
};
|
||||
+2
-1
@@ -189,7 +189,7 @@ describe('xterm.js', function() {
|
||||
it('should scroll down, when a key is pressed and terminal is scrolled up', function () {
|
||||
// Override evaluateKeyEscapeSequence to return cancel code
|
||||
xterm.evaluateKeyEscapeSequence = function() {
|
||||
return { cancel: true };
|
||||
return { key: 'a' };
|
||||
};
|
||||
var event = {
|
||||
type: 'keydown',
|
||||
@@ -200,6 +200,7 @@ describe('xterm.js', function() {
|
||||
|
||||
xterm.ydisp = 0;
|
||||
xterm.ybase = 40;
|
||||
assert.notEqual(xterm.ydisp, xterm.ybase);
|
||||
xterm.keyDown(event);
|
||||
|
||||
// Ensure that now the terminal is scrolled to bottom
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Browser utilities module. This module contains attributes and methods to help with
|
||||
* identifying the current browser and platform.
|
||||
* Attributes and methods to help with identifying the current browser and platform.
|
||||
* @module xterm/utils/Browser
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { contains } from './Generic.js';
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic utilities module. This module contains generic methods that can be helpful at
|
||||
* different parts of the code base.
|
||||
* Generic utilities module with methods that can be helpful at different parts of the code base.
|
||||
* @module xterm/utils/Generic
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { assert } from 'chai';
|
||||
import * as Keyboard from './Keyboard';
|
||||
|
||||
describe('Keyboard', () => {
|
||||
describe('isModifierOnlyKeyboardEvent', () => {
|
||||
it('should return true when only modifier keys are used', () => {
|
||||
// Note that KeyboardEvent.keyCode is deprecated but we're using it to improve browser
|
||||
// compatibility. This helper returns the `any` type because KeyboardEvent doesn't exist under
|
||||
// NodeJS.
|
||||
function createEvent(keyCode: number): any {
|
||||
return { keyCode };
|
||||
}
|
||||
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(16)));
|
||||
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(17)));
|
||||
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(18)));
|
||||
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(91)));
|
||||
assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(19)));
|
||||
assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(90)));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Keyboard utilities module. This module contains utilities for dealing with keyboard interaction.
|
||||
* @module xterm/utils/Keyboard
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets whether a KeyboardEvent is made up entirely of modifier keys.
|
||||
*
|
||||
* @param event The event to check.
|
||||
* @return Whether the KeyboardEvent is made up entirely of modifier keys.
|
||||
*/
|
||||
export function isModifierOnlyKeyboardEvent(event: KeyboardEvent): boolean {
|
||||
return event.keyCode === 16 || // Shift
|
||||
event.keyCode === 17 || // Control
|
||||
event.keyCode === 18 || // Alt
|
||||
event.keyCode === 91; // Meta
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -10;
|
||||
|
||||
+14
-32
@@ -1,27 +1,5 @@
|
||||
/**
|
||||
* xterm.js: xterm, in the browser
|
||||
* Copyright (c) 2014-2014, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
@@ -29,6 +7,7 @@
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CompositionHelper } from './CompositionHelper.js';
|
||||
@@ -807,7 +786,7 @@ Terminal.prototype.bindMouse = function() {
|
||||
pos.x -= 32;
|
||||
pos.y -= 32;
|
||||
self.send('\x1b[<'
|
||||
+ ((button & 3) === 3 ? button & ~3 : button)
|
||||
+ (((button & 3) === 3 ? button & ~3 : button) - 32)
|
||||
+ ';'
|
||||
+ pos.x
|
||||
+ ';'
|
||||
@@ -2430,12 +2409,10 @@ Terminal.prototype.keyDown = function(ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scroll down to prompt, whenever the user presses a key.
|
||||
if (!Keyboard.isModifierOnlyKeyboardEvent(ev) && this.ybase !== this.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
|
||||
if (this.ybase !== this.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2724,10 +2701,13 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
|
||||
// delete
|
||||
result.key = String.fromCharCode(127);
|
||||
} else if (ev.keyCode === 219) {
|
||||
// ^[ - escape
|
||||
// ^[ - Control Sequence Introducer (CSI)
|
||||
result.key = String.fromCharCode(27);
|
||||
} else if (ev.keyCode === 220) {
|
||||
// ^\ - String Terminator (ST)
|
||||
result.key = String.fromCharCode(28);
|
||||
} else if (ev.keyCode === 221) {
|
||||
// ^] - group sep
|
||||
// ^] - Operating System Command (OSC)
|
||||
result.key = String.fromCharCode(29);
|
||||
}
|
||||
} else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
@@ -3148,6 +3128,10 @@ Terminal.prototype.is = function(term) {
|
||||
* @param {string} data The data to populate in the event.
|
||||
*/
|
||||
Terminal.prototype.handler = function(data) {
|
||||
// Input is being sent to the terminal, the terminal should focus the prompt.
|
||||
if (this.ybase !== this.ydisp) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
this.emit('data', data);
|
||||
};
|
||||
|
||||
@@ -5130,8 +5114,6 @@ var wcwidth = (function(opts) {
|
||||
*/
|
||||
|
||||
Terminal.EventEmitter = EventEmitter;
|
||||
Terminal.CompositionHelper = CompositionHelper;
|
||||
Terminal.Viewport = Viewport;
|
||||
Terminal.inherits = inherits;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user