Merge branch 'master' into ctrl-alt-sequences

This commit is contained in:
Saad Malik
2018-02-08 12:03:38 -08:00
committed by GitHub
24 changed files with 3762 additions and 79 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:6
FROM node:8
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
# Set the working directory
+1
View File
@@ -1 +1,2 @@
web: npm start
webpack: npm run watch
+42 -9
View File
@@ -24,7 +24,7 @@ It enables applications to provide fully featured terminals to their users and c
First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running:
```
npm install
npm install xterm
```
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
@@ -131,27 +131,60 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](ht
## Demo
### Linux or macOS
Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host.
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run these commands:
Below you can find instructions on how to run the demo on different platforms.
### SourceLair
SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs).
Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo.
### 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:
```
docker-compose up
```
Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port.
### Foreman (or other Procfile runner)
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
```
npm install
npm start
foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner
```
Then open http://0.0.0.0:3000 in a web browser.
Then open http://0.0.0.0:3000 in a web browser to access the demo.
### Linux or macOS
First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal:
```
npm install
npm start # Run this in its own terminal
npm run watch # Run this in its own terminal
```
Then open http://0.0.0.0:3000 in a web browser to access the demo.
### Windows
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands.
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal:
```
npm install
npm start
npm start # Run this in its own terminal
npm run watch # Run this in its own terminal
```
Then open http://127.0.0.1:3000 in a web browser.
Then open http://127.0.0.1:3000 in a web browser to access the demo.
*Note: Do not use ConEmu, as it seems to break the demo for some reason.*
@@ -175,6 +208,6 @@ To contribute either code, documentation or issues to xterm.js please read the [
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+5 -1
View File
@@ -64,10 +64,14 @@
<label for="rows">Rows</label>
<input type="number" id="rows" />
</div>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
</div>
</div>
</div>
</div>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<script src="dist/bundle.js" defer ></script>
</body>
</html>
+11 -6
View File
@@ -33,23 +33,27 @@ var terminalContainer = document.getElementById('terminal-container'),
bellStyle: document.querySelector('#option-bell-style')
},
colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows');
rowsElement = document.getElementById('rows'),
paddingElement = document.getElementById('padding');
function setTerminalSize() {
var cols = parseInt(colsElement.value, 10);
var rows = parseInt(rowsElement.value, 10);
var viewportElement = document.querySelector('.xterm-viewport');
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px';
var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px';
var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.resize(cols, rows);
term.fit();
}
function setPadding() {
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
term.fit();
}
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
paddingElement.addEventListener('change', setPadding);
actionElements.findNext.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
@@ -119,6 +123,7 @@ function createTerminal() {
setTimeout(function () {
colsElement.value = term.cols;
rowsElement.value = term.rows;
paddingElement.value = 0;
// Set terminal size again to set the specific dimensions on the demo
setTerminalSize();
+7
View File
@@ -7,3 +7,10 @@ services:
- ./:/usr/src/app
ports:
- ${XTERMJS_PORT:3000}:3000
command: ["npm", "start"]
watch:
build: .
volumes:
- ./:/usr/src/app
command: ["npm", "run", "watch"]
+2
View File
@@ -148,6 +148,7 @@ namespace methods_core {
const r24: string = t.getOption('fontWeight');
const r25: string = t.getOption('fontWeightBold');
const r26: boolean = t.getOption('allowTransparency');
const r27: boolean = t.getOption('rightClickSelectsWord');
}
{
const t: Terminal = new Terminal();
@@ -187,6 +188,7 @@ namespace methods_core {
t.setOption('fontFamily', 'foo');
t.setOption('theme', {background: '#ff0000'});
t.setOption('macOptionIsMeta', true);
t.setOption('rightClickSelectsWord', false);
}
}
namespace scrolling {
+4
View File
@@ -192,6 +192,10 @@ gulp.task('webpack', ['build'], function() {
.pipe(gulp.dest('demo/dist/'));
});
gulp.task('watch', ['webpack'], () => {
gulp.watch(['./src/*', './src/**/*'], ['webpack']);
});
/**
* Submit coverage results to coveralls.io
*/
+3522 -4
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -67,7 +67,7 @@
"nodemon": "1.10.2",
"sorcery": "^0.10.0",
"tslint": "^5.9.1",
"typescript": "~2.4.0",
"typescript": "~2.7.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^3.10.0",
@@ -75,7 +75,6 @@
"zmodem.js": "^0.1.5"
},
"scripts": {
"prestart": "gulp webpack",
"start": "node demo/app",
"prestart-zmodem": "npm run build",
"start-zmodem": "node build/addons/zmodem/demo/app",
@@ -86,7 +85,8 @@
"build": "gulp build",
"prepublish": "npm run build",
"coveralls": "gulp coveralls",
"webpack": "gulp webpack"
"webpack": "gulp webpack",
"watch": "gulp watch"
},
"dependencies": {}
}
+7
View File
@@ -150,6 +150,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
validationCallback: options.validationCallback,
hoverTooltipCallback: options.tooltipCallback,
hoverLeaveCallback: options.leaveCallback,
willLinkActivate: options.willLinkActivate,
priority: options.priority || 0
};
this._addLinkMatcherToList(matcher);
@@ -290,6 +291,12 @@ export class Linkifier extends EventEmitter implements ILinkifier {
if (matcher.hoverLeaveCallback) {
matcher.hoverLeaveCallback();
}
},
e => {
if (matcher.willLinkActivate) {
return matcher.willLinkActivate(e, uri);
}
return true;
}
));
}
+1 -1
View File
@@ -28,7 +28,7 @@ class TestSelectionManager extends SelectionManager {
public get model(): SelectionModel { return this._model; }
public selectLineAt(line: number): void { this._selectLineAt(line); }
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords); }
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
// Disable DOM interaction
public enable(): void {}
+43 -7
View File
@@ -253,6 +253,37 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });
}
/**
* Checks if the current click was inside the current selection
* @param event The mouse event
*/
public isClickInSelection(event: MouseEvent): boolean {
const coords = this._getMouseBufferCoords(event);
const start = this._model.finalSelectionStart;
const end = this._model.finalSelectionEnd;
if (!start || !end) {
return false;
}
return (coords[1] > start[1] && coords[1] < end[1]) ||
(start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) ||
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]);
}
/**
* Selects word at the current mouse event coordinates.
* @param event The mouse event.
*/
public selectWordAtCursor(event: MouseEvent): void {
const coords = this._getMouseBufferCoords(event);
if (coords) {
this._selectWordAt(coords, false);
this._model.selectionEnd = null;
this.refresh(true);
}
}
/**
* Selects all text within the terminal.
*/
@@ -278,7 +309,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
if (!coords) {
return null;
}
@@ -298,7 +329,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1];
let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1];
const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);
if (offset >= 0 && offset <= terminalHeight) {
return 0;
@@ -445,7 +476,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
const coords = this._getMouseBufferCoords(event);
if (coords) {
this._activeSelectionMode = SelectionMode.WORD;
this._selectWordAt(coords);
this._selectWordAt(coords, true);
}
}
@@ -602,7 +633,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* Gets positional information for the word at the coordinated specified.
* @param coords The coordinates to get the word at.
*/
private _getWordAt(coords: [number, number]): IWordPosition {
private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition {
const bufferLine = this._buffer.lines.get(coords[1]);
if (!bufferLine) {
return null;
@@ -708,15 +739,20 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
- leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
return null;
}
return { start, length };
}
/**
* Selects the word at the coordinates specified.
* @param coords The coordinates to get the word at.
* @param allowWhitespaceOnlySelection If whitespace should be selected
*/
protected _selectWordAt(coords: [number, number]): void {
const wordPosition = this._getWordAt(coords);
protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
if (wordPosition) {
this._model.selectionStart = [wordPosition.start, coords[1]];
this._model.selectionStartLength = wordPosition.length;
@@ -728,7 +764,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param coords The coordinates to get the word at.
*/
private _selectToWordAt(coords: [number, number]): void {
const wordPosition = this._getWordAt(coords);
const wordPosition = this._getWordAt(coords, true);
if (wordPosition) {
this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];
}
+15 -14
View File
@@ -115,7 +115,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
useFlowControl: false,
allowTransparency: false,
tabStopWidth: 8,
theme: null
theme: null,
rightClickSelectsWord: Browser.isMac
// programFeatures: false,
// focusKeys: false,
};
@@ -123,6 +124,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal {
public textarea: HTMLTextAreaElement;
public element: HTMLElement;
public screenElement: HTMLElement;
/**
* The HTMLElement that the terminal is created in, set by Terminal.open.
@@ -534,12 +536,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Firefox doesn't appear to fire the contextmenu event on right click
on(this.element, 'mousedown', (event: MouseEvent) => {
if (event.button === 2) {
rightClickHandler(event, this.textarea, this.selectionManager);
rightClickHandler(event, this.textarea, this.selectionManager, this.options.rightClickSelectsWord);
}
});
} else {
on(this.element, 'contextmenu', (event: MouseEvent) => {
rightClickHandler(event, this.textarea, this.selectionManager);
rightClickHandler(event, this.textarea, this.selectionManager, this.options.rightClickSelectsWord);
});
}
@@ -636,15 +638,18 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.viewportScrollArea.classList.add('xterm-scroll-area');
this.viewportElement.appendChild(this.viewportScrollArea);
this._mouseZoneManager = new MouseZoneManager(this);
this.on('scroll', () => this._mouseZoneManager.clearAll());
this.linkifier.attachToDom(this._mouseZoneManager);
this.screenElement = document.createElement('div');
this.screenElement.classList.add('xterm-screen');
// Create the container that will hold helpers like the textarea for
// capturing DOM Events. Then produce the helpers.
this.helperContainer = document.createElement('div');
this.helperContainer.classList.add('xterm-helpers');
fragment.appendChild(this.helperContainer);
this.screenElement.appendChild(this.helperContainer);
fragment.appendChild(this.screenElement);
this._mouseZoneManager = new MouseZoneManager(this);
this.on('scroll', () => this._mouseZoneManager.clearAll());
this.linkifier.attachToDom(this._mouseZoneManager);
this.textarea = document.createElement('textarea');
this.textarea.classList.add('xterm-helper-textarea');
@@ -762,7 +767,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
button = getButton(ev);
// get mouse coordinates
pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
sendEvent(button, pos);
@@ -788,7 +793,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
function sendMove(ev: MouseEvent): void {
let button = pressed;
let pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
let pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
// buttons marked as motions
@@ -1631,8 +1636,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// page up
if (ev.shiftKey) {
result.scrollLines = -(this.rows - 1);
} else if (modifiers) {
result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[5~';
}
@@ -1641,8 +1644,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// page down
if (ev.shiftKey) {
result.scrollLines = this.rows - 1;
} else if (modifiers) {
result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[6~';
}
+13
View File
@@ -85,6 +85,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
}
export interface IViewport {
scrollBarWidth: number;
syncScrollArea(): void;
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
@@ -161,6 +162,7 @@ export interface ILinkMatcher {
matchIndex?: number;
validationCallback?: LinkMatcherValidationCallback;
priority?: number;
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface ICharset {
@@ -174,6 +176,7 @@ export interface ILinkHoverEvent {
}
export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
renderer: IRenderer;
@@ -187,6 +190,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
buffers: IBufferSet;
isFocused: boolean;
mouseHelper: IMouseHelper;
viewport: IViewport;
bracketedPasteMode: boolean;
applicationCursor: boolean;
@@ -285,6 +289,8 @@ export interface ISelectionManager {
disable(): void;
enable(): void;
setSelection(row: number, col: number, length: number): void;
isClickInSelection(event: MouseEvent): boolean;
selectWordAtCursor(event: MouseEvent): void;
}
export interface ILinkifier extends IEventEmitter {
@@ -321,6 +327,13 @@ export interface ILinkMatcherOptions {
* default value is 0.
*/
priority?: number;
/**
* A callback that fires when the mousedown and click events occur that
* determines whether a link will be activated upon click. This enables
* only activating a link when a certain modifier is held down, if not the
* mouse event will continue propagation (eg. double click to select word).
*/
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface IBrowser {
+15 -7
View File
@@ -7,11 +7,14 @@ import { IColorSet } from './renderer/Types';
import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './utils/CharMeasure';
const FALLBACK_SCROLL_BAR_WIDTH = 15;
/**
* 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.
*/
export class Viewport implements IViewport {
public scrollBarWidth: number = 0;
private currentRowHeight: number = 0;
private lastRecordedBufferLength: number = 0;
private lastRecordedViewportHeight: number = 0;
@@ -31,6 +34,10 @@ export class Viewport implements IViewport {
private scrollArea: HTMLElement,
private charMeasure: CharMeasure
) {
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
// therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
// Perform this async to ensure the CharMeasure is ready.
@@ -48,13 +55,8 @@ export class Viewport implements IViewport {
private refresh(): void {
if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) {
this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight;
this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px';
}
const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength);
this.lastRecordedViewportHeight = this.viewportElement.offsetHeight;
const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight);
if (this.lastRecordedBufferHeight !== newBufferHeight) {
this.lastRecordedBufferHeight = newBufferHeight;
this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';
@@ -93,6 +95,12 @@ export class Viewport implements IViewport {
* @param ev The scroll event.
*/
private onScroll(ev: Event): void {
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
// which causes the terminal to scroll the buffer to the top
if (!this.viewportElement.offsetParent) {
return;
}
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
const diff = newRow - this.terminal.buffer.ydisp;
this.terminal.scrollLines(diff, true);
+10 -5
View File
@@ -28,17 +28,22 @@ export function proposeGeometry(term: Terminal): IGeometry {
}
const parentElementStyle = window.getComputedStyle(term.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
const elementStyle = window.getComputedStyle(term.element);
const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
const elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
const elementPaddingHor = elementPadding.right + elementPadding.left;
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor;
const availableWidth = parentElementWidth - elementPaddingHor - (<any>term).viewport.scrollBarWidth;
const geometry = {
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
};
return geometry;
}
+6 -1
View File
@@ -116,10 +116,15 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
* @param ev The original right click event to be handled.
* @param textarea The terminal's textarea.
* @param selectionManager The terminal's selection manager.
* @param shouldSelectWord If true and there is no selection the current word will be selected
*/
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager): void {
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void {
moveTextAreaUnderMouseCursor(ev, textarea);
if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) {
selectionManager.selectWordAtCursor(ev);
}
// Get textarea ready to copy from the context menu
textarea.value = selectionManager.selectionText;
textarea.select();
+9 -8
View File
@@ -151,10 +151,10 @@ export class MouseZoneManager implements IMouseZoneManager {
// components from handling the mouse event.
const zone = this._findZoneEventAt(e);
if (zone) {
// TODO: When link modifier support is added, the event should only be
// cancelled when the modifier is held (see #1021)
e.preventDefault();
e.stopImmediatePropagation();
if (zone.willLinkActivate(e)) {
e.preventDefault();
e.stopImmediatePropagation();
}
}
}
@@ -169,7 +169,7 @@ export class MouseZoneManager implements IMouseZoneManager {
}
private _findZoneEventAt(e: MouseEvent): IMouseZone {
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);
const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);
if (!coords) {
return null;
}
@@ -189,9 +189,10 @@ export class MouseZone implements IMouseZone {
public x2: number,
public y: number,
public clickCallback: (e: MouseEvent) => any,
public hoverCallback?: (e: MouseEvent) => any,
public tooltipCallback?: (e: MouseEvent) => any,
public leaveCallback?: () => void
public hoverCallback: (e: MouseEvent) => any,
public tooltipCallback: (e: MouseEvent) => any,
public leaveCallback: () => void,
public willLinkActivate: (e: MouseEvent) => boolean
) {
}
}
+4 -3
View File
@@ -13,7 +13,8 @@ export interface IMouseZone {
x2: number;
y: number;
clickCallback: (e: MouseEvent) => any;
hoverCallback?: (e: MouseEvent) => any;
tooltipCallback?: (e: MouseEvent) => any;
leaveCallback?: () => any;
hoverCallback: (e: MouseEvent) => any | undefined;
tooltipCallback: (e: MouseEvent) => any | undefined;
leaveCallback: () => any | undefined;
willLinkActivate: (e: MouseEvent) => boolean;
}
+8 -4
View File
@@ -38,10 +38,10 @@ export class Renderer extends EventEmitter implements IRenderer {
}
this._renderLayers = [
new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors),
new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal),
new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors)
new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency),
new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors),
new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal),
new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)
];
this.dimensions = {
scaledCharWidth: null,
@@ -120,6 +120,10 @@ export class Renderer extends EventEmitter implements IRenderer {
this._terminal.refresh(0, this._terminal.rows - 1);
}
// Resize the screen
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth + this._terminal.viewport.scrollBarWidth}px`;
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
this.emit('resize', {
width: this.dimensions.canvasWidth,
height: this.dimensions.canvasHeight
+3
View File
@@ -80,6 +80,7 @@ export class MockTerminal implements ITerminal {
isFocused: boolean;
options: ITerminalOptions = {};
element: HTMLElement;
screenElement: HTMLElement;
rowContainer: HTMLElement;
selectionContainer: HTMLElement;
selectionManager: ISelectionManager;
@@ -96,6 +97,7 @@ export class MockTerminal implements ITerminal {
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
viewport: IViewport;
applicationCursor: boolean;
handler(data: string): void {
throw new Error('Method not implemented.');
@@ -308,6 +310,7 @@ export class MockRenderer implements IRenderer {
}
export class MockViewport implements IViewport {
scrollBarWidth: number = 0;
onThemeChanged(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
+10 -1
View File
@@ -96,9 +96,18 @@
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm canvas {
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
+20 -4
View File
@@ -55,6 +55,8 @@ declare module 'xterm' {
/**
* Whether to enable the rendering of bold text.
*
* @deprecated Use fontWeight and fontWeightBold instead.
*/
enableBold?: boolean;
@@ -93,6 +95,12 @@ declare module 'xterm' {
*/
macOptionIsMeta?: boolean;
/**
* Whether to select the word under the cursor on right click, this is
* standard behavior in a lot of macOS applications.
*/
rightClickSelectsWord?: boolean;
/**
* The number of rows in the terminal.
*/
@@ -174,8 +182,8 @@ declare module 'xterm' {
matchIndex?: number;
/**
* A callback that validates an individual link, returning true if valid and
* false if invalid.
* A callback that validates whether to create an individual link, pass
* whether the link is valid to the callback.
*/
validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
@@ -196,6 +204,14 @@ declare module 'xterm' {
* default value is 0.
*/
priority?: number;
/**
* A callback that fires when the mousedown and click events occur that
* determines whether a link will be activated upon click. This enables
* only activating a link when a certain modifier is held down, if not the
* mouse event will continue propagation (eg. double click to select word).
*/
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface IEventEmitter {
@@ -427,7 +443,7 @@ declare module 'xterm' {
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
@@ -478,7 +494,7 @@ declare module 'xterm' {
* @param key The option key.
* @param value The option value.
*/
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
/**
* Sets an option on the terminal.
* @param key The option key.