import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './EventEmitter';
import { Viewport } from './Viewport';
-import { rightClickHandler, pasteHandler, copyHandler, prepareTextForClipboard } from './handlers/Clipboard';
+import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
import { CircularList } from './utils/CircularList';
import { C0 } from './EscapeSequences';
import { InputHandler } from './InputHandler';
this.queue = '';
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
- this.customKeydownHandler = null;
+ this.customKeyEventHandler = null;
this.cursorBlinkInterval = null;
// modes
on(this.textarea, 'paste', pasteHandlerWrapper);
on(this.element, 'paste', pasteHandlerWrapper);
+ // Handle right click context menus
if (term.browser.isFirefox) {
+ // Firefox doesn't appear to fire the contextmenu event on right click
on(this.element, 'mousedown', event => {
- if (ev.button == 2) {
+ if (event.button == 2) {
rightClickHandler(event, this.textarea, this.selectionManager);
}
});
rightClickHandler(event, this.textarea, this.selectionManager);
});
}
+
+ // Move the textarea under the cursor when middle clicking on Linux to ensure
+ // middle click to paste selection works. This only appears to work in Chrome
+ // at the time is writing.
+ if (term.browser.isLinux) {
+ // Use auxclick event over mousedown the latter doesn't seem to work. Note
+ // that the regular click event doesn't fire for the middle mouse button.
+ on(this.element, 'auxclick', event => {
+ if (event.button === 1) {
+ moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);
+ }
+ });
+ }
};
/**
this.element.classList.add('xterm-theme-' + this.theme);
this.setCursorBlinking(this.options.cursorBlink);
- this.element.style.height;
this.element.setAttribute('tabindex', 0);
this.viewportElement = document.createElement('div');
this.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea);
- // Create the selection container. This needs to be added before the
- // rowContainer as the selection must be below the text.
+ // Create the selection container.
this.selectionContainer = document.createElement('div');
this.selectionContainer.classList.add('xterm-selection');
this.element.appendChild(this.selectionContainer);
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
this.renderer = new Renderer(this);
this.selectionManager = new SelectionManager(this, this.lines, this.rowContainer, this.charMeasure);
- this.selectionManager.on('refresh', data => this.renderer.refreshSelection(data.start, data.end));
+ this.selectionManager.on('refresh', data => {
+ this.renderer.refreshSelection(data.start, data.end);
+ });
+ this.selectionManager.on('newselection', text => {
+ // If there's a new selection, put it into the textarea, focus and select it
+ // in order to register it as a selection on the OS. This event is fired
+ // only on Linux to enable middle click to paste selection.
+ this.textarea.value = text;
+ this.textarea.focus();
+ this.textarea.select();
+ });
this.on('scroll', () => this.selectionManager.refresh());
this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
Terminal.prototype.updateCharSizeStyles = function() {
this.charSizeStyleElement.textContent =
`.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +
- `.xterm-normal-char{width:${this.charMeasure.width}px;}`;
+ `.xterm-normal-char{width:${this.charMeasure.width}px;}` +
+ `.xterm-rows > div{height:${this.charMeasure.height}px;}`;
}
/**
this.linkifier.linkifyRow(i);
}
}
-}
+};
/**
* Display the cursor element
/**
* Scroll the terminal down 1 row, creating a blank line.
+ * @param {boolean} isWrapped Whether the new line is wrapped from the previous
+ * line.
*/
-Terminal.prototype.scroll = function() {
+Terminal.prototype.scroll = function(isWrapped) {
var row;
// Make room for the new row in lines
if (row === this.lines.length) {
// Optimization: pushing is faster than splicing when they amount to the same behavior
- this.lines.push(this.blankLine());
+ this.lines.push(this.blankLine(undefined, isWrapped));
} else {
// add our new line
- this.lines.splice(row, 0, this.blankLine());
+ this.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
}
if (this.scrollTop !== 0) {
*/
Terminal.prototype.scrollPages = function(pageCount) {
this.scrollDisp(pageCount * (this.rows - 1));
-}
+};
/**
* Scrolls the display of the terminal to the top.
*/
Terminal.prototype.scrollToTop = function() {
this.scrollDisp(-this.ydisp);
-}
+};
/**
* Scrolls the display of the terminal to the bottom.
*/
Terminal.prototype.scrollToBottom = function() {
this.scrollDisp(this.ybase - this.ydisp);
-}
+};
/**
* Writes text to the terminal.
- * @param {string} text The text to write to the terminal.
+ * @param {string} data The text to write to the terminal.
*/
Terminal.prototype.write = function(data) {
this.writeBuffer.push(data);
self.innerWrite();
});
}
-}
+};
Terminal.prototype.innerWrite = function() {
var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
/**
* Writes text to the terminal, followed by a break line character (\n).
- * @param {string} text The text to write to the terminal.
+ * @param {string} data The text to write to the terminal.
*/
Terminal.prototype.writeln = function(data) {
this.write(data + '\r\n');
};
/**
- * Attaches a custom keydown handler which is run before keys are processed, giving consumers of
- * xterm.js ultimate control as to what keys should be processed by the terminal and what keys
- * should not.
+ * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.
* @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a
* function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent
* the default action. The function returns whether the event should be processed by xterm.js.
*/
Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {
- this.customKeydownHandler = customKeydownHandler;
-}
+ let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';
+ console.warn(message);
+ this.attachCustomKeyEventHandler(customKeydownHandler);
+};
+
+/**
+ * Attaches a custom key event handler which is run before keys are processed, giving consumers of
+ * xterm.js ultimate control as to what keys should be processed by the terminal and what keys
+ * should not.
+ * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a
+ * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent
+ * the default action. The function returns whether the event should be processed by xterm.js.
+ */
+Terminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {
+ 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 {LinkHandler} handler The handler callback function.
+ * @param {handler} handler The handler callback function.
*/
Terminal.prototype.setHypertextLinkHandler = function(handler) {
if (!this.linkifier) {
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 {LinkMatcherValidationCallback} callback The callback to use, this can
+ * @param {LinkMatcherValidationCallback} handler The callback to use, this can
* be cleared with null.
*/
Terminal.prototype.setHypertextValidationCallback = function(handler) {
this.linkifier.setHypertextValidationCallback(handler);
// Refresh to force links to refresh
this.refresh(0, this.rows - 1);
-}
+};
/**
* Registers a link matcher, allowing custom link patterns to be matched and
* @param {RegExp} regex The regular expression to search for, specifically
* this searches the textContent of the rows. You will want to use \s to match
* a space ' ' character for example.
- * @param {LinkHandler} handler The callback when the link is called.
+ * @param {handler} handler The callback when the link is called.
* @param {LinkMatcherOptions} [options] Options for the link matcher.
* @return {number} The ID of the new matcher, this can be used to deregister.
*/
this.refresh(0, this.rows - 1);
return matcherId;
}
-}
+};
/**
* Deregisters a link matcher if it has been registered.
this.refresh(0, this.rows - 1);
}
}
-}
+};
/**
* Gets whether the terminal has an active selection.
*/
Terminal.prototype.hasSelection = function() {
return this.selectionManager.hasSelection;
-}
+};
/**
* Gets the terminal's current selection, this is useful for implementing copy
* behavior outside of xterm.js.
*/
Terminal.prototype.getSelection = function() {
- // TODO: Should prepareTextForClipboard logic be moved to SelectionManager?
- return prepareTextForClipboard(this.selectionManager.selectionText);
-}
+ return this.selectionManager.selectionText;
+};
+
+/**
+ * Clears the current terminal selection.
+ */
+Terminal.prototype.clearSelection = function() {
+ this.selectionManager.clearSelection();
+};
/**
* Selects all text within the terminal.
*/
Terminal.prototype.selectAll = function() {
this.selectionManager.selectAll();
-}
+};
/**
* Handle a keydown event
* @param {KeyboardEvent} ev The keydown event to be handled.
*/
Terminal.prototype.keyDown = function(ev) {
- if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) {
+ if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
return false;
}
Terminal.prototype.keyPress = function(ev) {
var key;
+ if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
+ return false;
+ }
+
this.cancel(ev);
if (ev.charCode) {
this.showCursor();
this.handler(key);
- return false;
+ return true;
};
/**
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.ybase--;
- addToY++
+ addToY++;
if (this.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
/**
* Return the data array of a blank line
* @param {number} cur First bunch of data for each "blank" character.
+ * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.
*/
-Terminal.prototype.blankLine = function(cur) {
+Terminal.prototype.blankLine = function(cur, isWrapped) {
var attr = cur
? this.eraseAttr()
: this.defAttr;
, line = []
, i = 0;
+ // TODO: It is not ideal that this is a property on an array, a buffer line
+ // class should be added that will hold this data and other useful functions.
+ if (isWrapped) {
+ line.isWrapped = isWrapped;
+ }
+
for (; i < this.cols; i++) {
line[i] = ch;
}
Terminal.prototype.reset = function() {
this.options.rows = this.rows;
this.options.cols = this.cols;
- var customKeydownHandler = this.customKeydownHandler;
+ var customKeyEventHandler = this.customKeyEventHandler;
var cursorBlinkInterval = this.cursorBlinkInterval;
Terminal.call(this, this.options);
- this.customKeydownHandler = customKeydownHandler;
+ this.customKeyEventHandler = customKeyEventHandler;
this.cursorBlinkInterval = cursorBlinkInterval;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();