Merge remote-tracking branch 'ups/master' into 1221_iterminal_typings
This commit is contained in:
+4
-12
@@ -120,11 +120,6 @@ export class Buffer implements IBuffer {
|
||||
if (this._terminal.cols < newCols) {
|
||||
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
|
||||
for (let i = 0; i < this._lines.length; i++) {
|
||||
// TODO: This should be removed, with tests setup for the case that was
|
||||
// causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824
|
||||
if (this._lines.get(i) === undefined) {
|
||||
this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));
|
||||
}
|
||||
while (this._lines.get(i).length < newCols) {
|
||||
this._lines.get(i).push(ch);
|
||||
}
|
||||
@@ -181,16 +176,13 @@ export class Buffer implements IBuffer {
|
||||
}
|
||||
|
||||
// Make sure that the cursor stays on screen
|
||||
if (this.y >= newRows) {
|
||||
this.y = newRows - 1;
|
||||
}
|
||||
this.x = Math.min(this.x, newCols - 1);
|
||||
this.y = Math.min(this.y, newRows - 1);
|
||||
if (addToY) {
|
||||
this.y += addToY;
|
||||
}
|
||||
|
||||
if (this.x >= newCols) {
|
||||
this.x = newCols - 1;
|
||||
}
|
||||
this.savedY = Math.min(this.savedY, newRows - 1);
|
||||
this.savedX = Math.min(this.savedX, newCols - 1);
|
||||
|
||||
this.scrollTop = 0;
|
||||
}
|
||||
|
||||
+14
-3
@@ -61,24 +61,35 @@ export class BufferSet extends EventEmitter implements IBufferSet {
|
||||
* Sets the normal Buffer of the BufferSet as its currently active Buffer
|
||||
*/
|
||||
public activateNormalBuffer(): void {
|
||||
if (this._activeBuffer === this._normal) {
|
||||
return;
|
||||
}
|
||||
// The alt buffer should always be cleared when we switch to the normal
|
||||
// buffer. This frees up memory since the alt buffer should always be new
|
||||
// when activated.
|
||||
this._alt.clear();
|
||||
|
||||
this._activeBuffer = this._normal;
|
||||
this.emit('activate', this._normal);
|
||||
this.emit('activate', {
|
||||
activeBuffer: this._normal,
|
||||
inactiveBuffer: this._alt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alt Buffer of the BufferSet as its currently active Buffer
|
||||
*/
|
||||
public activateAltBuffer(): void {
|
||||
if (this._activeBuffer === this._alt) {
|
||||
return;
|
||||
}
|
||||
// Since the alt buffer is always cleared when the normal buffer is
|
||||
// activated, we want to fill it when switching to it.
|
||||
this._alt.fillViewportRows();
|
||||
this._activeBuffer = this._alt;
|
||||
this.emit('activate', this._alt);
|
||||
this.emit('activate', {
|
||||
activeBuffer: this._alt,
|
||||
inactiveBuffer: this._normal
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -928,7 +928,6 @@ export class InputHandler implements IInputHandler {
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
this._terminal.buffers.activateAltBuffer();
|
||||
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
@@ -1100,7 +1099,6 @@ export class InputHandler implements IInputHandler {
|
||||
// if (params[0] === 1049) {
|
||||
// this.restoreCursor(params);
|
||||
// }
|
||||
this._terminal.selectionManager.setBuffer(this._terminal.buffer);
|
||||
this._terminal.refresh(0, this._terminal.rows - 1);
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
|
||||
@@ -7,8 +7,6 @@ import { IEventEmitter } from 'xterm';
|
||||
import { ITerminalOptions, ILinkMatcherOptions, IMouseZoneManager, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, IColorSet, IRenderer, IBufferSet, IBuffer, ISelectionManager } from '../typings/xterm-internal';
|
||||
import { ICharset } from './Interfaces';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This interface encapsulates everything needed from the Terminal by the
|
||||
* InputHandler. This cleanly separates the large amount of methods needed by
|
||||
|
||||
+3
-3
@@ -82,12 +82,12 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
}
|
||||
|
||||
// Increase range to linkify
|
||||
if (!this._rowsToLinkify.start) {
|
||||
if (this._rowsToLinkify.start === null) {
|
||||
this._rowsToLinkify.start = start;
|
||||
this._rowsToLinkify.end = end;
|
||||
} else {
|
||||
this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start;
|
||||
this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end;
|
||||
this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start);
|
||||
this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end);
|
||||
}
|
||||
|
||||
// Clear out any existing links on this row range
|
||||
|
||||
@@ -20,10 +20,9 @@ class TestMockTerminal extends MockTerminal {
|
||||
class TestSelectionManager extends SelectionManager {
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
buffer: IBuffer,
|
||||
charMeasure: CharMeasure
|
||||
) {
|
||||
super(terminal, buffer, charMeasure);
|
||||
super(terminal, charMeasure);
|
||||
}
|
||||
|
||||
public get model(): SelectionModel { return this._model; }
|
||||
@@ -58,7 +57,7 @@ describe('SelectionManager', () => {
|
||||
terminal.buffers = new BufferSet(terminal);
|
||||
terminal.buffer = terminal.buffers.active;
|
||||
buffer = terminal.buffer;
|
||||
selectionManager = new TestSelectionManager(terminal, buffer, null);
|
||||
selectionManager = new TestSelectionManager(terminal, null);
|
||||
});
|
||||
|
||||
function stringToRow(text: string): LineData {
|
||||
|
||||
+23
-17
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData } from '../typings/xterm-internal';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import * as Browser from './utils/Browser';
|
||||
import * as Browser from './shared/utils/Browser';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import { CircularList } from './utils/CircularList';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
@@ -94,10 +94,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
|
||||
private _mouseMoveListener: EventListener;
|
||||
private _mouseUpListener: EventListener;
|
||||
private _trimListener: (...args: any[]) => void;
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _buffer: IBuffer,
|
||||
private _charMeasure: CharMeasure
|
||||
) {
|
||||
super();
|
||||
@@ -108,18 +108,24 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._activeSelectionMode = SelectionMode.NORMAL;
|
||||
}
|
||||
|
||||
private get _buffer(): IBuffer {
|
||||
return this._terminal.buffers.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes listener variables.
|
||||
*/
|
||||
private _initListeners(): void {
|
||||
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
|
||||
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
|
||||
this._trimListener = (amount: number) => this._onTrim(amount);
|
||||
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
this._buffer.lines.on('trim', (amount: number) => this._onTrim(amount));
|
||||
this.initBuffersListeners();
|
||||
}
|
||||
|
||||
public initBuffersListeners(): void {
|
||||
this._terminal.buffer.lines.on('trim', this._trimListener);
|
||||
this._terminal.buffers.on('activate', e => this._onBufferActivate(e));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,16 +144,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active buffer, this should be called when the alt buffer is
|
||||
* switched in or out.
|
||||
* @param buffer The active buffer.
|
||||
*/
|
||||
public setBuffer(buffer: IBuffer): void {
|
||||
this._buffer = buffer;
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
|
||||
public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }
|
||||
|
||||
@@ -546,6 +542,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._terminal.emit('selection');
|
||||
}
|
||||
|
||||
private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
|
||||
this.clearSelection();
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
e.inactiveBuffer.lines.off('trim', this._trimListener);
|
||||
e.activeBuffer.lines.on('trim', this._trimListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a viewport column to the character index on the buffer line, the
|
||||
* latter takes into account wide characters.
|
||||
|
||||
@@ -503,6 +503,9 @@ describe('term.js addons', () => {
|
||||
it('should return \\x1b[5C for alt+right', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
|
||||
});
|
||||
});
|
||||
|
||||
describe('On macOS platforms', () => {
|
||||
@@ -515,6 +518,19 @@ describe('term.js addons', () => {
|
||||
it('should return \\x1bf for alt+right', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with macOptionIsMeta', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
term.setOption('macOptionIsMeta', true);
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return \\x1b[5A for alt+up', () => {
|
||||
@@ -597,6 +613,22 @@ describe('term.js addons', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('with macOptionIsMeta', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
term.setOption('macOptionIsMeta', true);
|
||||
});
|
||||
|
||||
it('should interfere with the alt key on keyDown', () => {
|
||||
evKeyDown.altKey = true;
|
||||
evKeyDown.keyCode = 81;
|
||||
assert.equal(term.keyDown(evKeyDown), false);
|
||||
evKeyDown.altKey = true;
|
||||
evKeyDown.keyCode = 192;
|
||||
assert.equal(term.keyDown(evKeyDown), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('On Mac OS', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
|
||||
+46
-34
@@ -36,7 +36,7 @@ import { Renderer } from './renderer/Renderer';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { SelectionManager } from './SelectionManager';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
import * as Browser from './utils/Browser';
|
||||
import * as Browser from './shared/utils/Browser';
|
||||
import { MouseHelper } from './utils/MouseHelper';
|
||||
import { CHARSETS } from './Charsets';
|
||||
import { CustomKeyEventHandler } from './Types';
|
||||
@@ -44,7 +44,6 @@ import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper } from
|
||||
import { BELL_SOUND } from './utils/Sounds';
|
||||
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
||||
import { MouseZoneManager } from './input/MouseZoneManager';
|
||||
import { initialize as initializeCharAtlas } from './renderer/CharAtlas';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||
@@ -74,11 +73,14 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
enableBold: true,
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
fontWeight: 'normal',
|
||||
fontWeightBold: 'bold',
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
scrollback: 1000,
|
||||
screenKeys: false,
|
||||
debug: false,
|
||||
macOptionIsMeta: false,
|
||||
cancelEvents: false,
|
||||
disableStdin: false,
|
||||
useFlowControl: false,
|
||||
@@ -191,7 +193,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public selectionManager: SelectionManager;
|
||||
public linkifier: ILinkifier;
|
||||
public buffers: BufferSet;
|
||||
public buffer: Buffer;
|
||||
public viewport: IViewport;
|
||||
private compositionHelper: ICompositionHelper;
|
||||
public charMeasure: CharMeasure;
|
||||
@@ -292,17 +293,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
// Create the terminal's buffers and set the current buffer
|
||||
this.buffers = new BufferSet(this);
|
||||
this.buffer = this.buffers.active; // Convenience shortcut;
|
||||
this.buffers.on('activate', (buffer: Buffer) => {
|
||||
this.buffer = buffer;
|
||||
});
|
||||
|
||||
// Ensure the selection manager has the correct buffer
|
||||
if (this.selectionManager) {
|
||||
this.selectionManager.setBuffer(this.buffer);
|
||||
this.selectionManager.clearSelection();
|
||||
this.selectionManager.initBuffersListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience property to active buffer.
|
||||
*/
|
||||
public get buffer(): Buffer {
|
||||
return this.buffers.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* back_color_erase feature for xterm.
|
||||
*/
|
||||
@@ -360,6 +363,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
value = 'block';
|
||||
}
|
||||
break;
|
||||
case 'fontWeight':
|
||||
if (!value) {
|
||||
value = 'normal';
|
||||
}
|
||||
break;
|
||||
case 'fontWeightBold':
|
||||
if (!value) {
|
||||
value = 'bold';
|
||||
}
|
||||
break;
|
||||
case 'lineHeight':
|
||||
if (value < 1) {
|
||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||
@@ -413,11 +426,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
case 'enableBold':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold' || key === 'enableBold');
|
||||
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
this.renderer.clear();
|
||||
this.renderer.onResize(this.cols, this.rows, false);
|
||||
this.renderer.onResize(this.cols, this.rows, didCharSizeChange);
|
||||
this.refresh(0, this.rows - 1);
|
||||
// this.charMeasure.measure(this.options);
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
this.viewport.syncScrollArea();
|
||||
@@ -573,8 +589,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.document = this.parent.ownerDocument;
|
||||
this.body = <HTMLBodyElement>this.document.body;
|
||||
|
||||
initializeCharAtlas(this.document);
|
||||
|
||||
// Create main element container
|
||||
this.element = this.document.createElement('div');
|
||||
this.element.classList.add('terminal');
|
||||
@@ -639,7 +653,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));
|
||||
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
||||
|
||||
this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure);
|
||||
this.selectionManager = new SelectionManager(this, this.charMeasure);
|
||||
this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e));
|
||||
this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end));
|
||||
this.selectionManager.on('newselection', text => {
|
||||
@@ -1372,7 +1386,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return this.cancel(ev, true);
|
||||
}
|
||||
|
||||
if (isThirdLevelShift(this.browser, ev)) {
|
||||
if (this._isThirdLevelShift(this.browser, ev)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1393,6 +1407,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
return this.cancel(ev, true);
|
||||
}
|
||||
|
||||
private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
}
|
||||
|
||||
// Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
|
||||
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that determines how a KeyboardEvent should be handled. The key of the
|
||||
* returned value is the new key code to pass to the PTY.
|
||||
@@ -1697,8 +1724,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// ^] - Operating System Command (OSC)
|
||||
result.key = String.fromCharCode(29);
|
||||
}
|
||||
} else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
// On Mac this is a third level shift. Use <Esc> instead.
|
||||
} else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
|
||||
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||||
result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);
|
||||
} else if (ev.keyCode === 192) {
|
||||
@@ -1764,7 +1791,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
}
|
||||
|
||||
if (!key || (
|
||||
(ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev)
|
||||
(ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -2078,11 +2105,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.options.cols = this.cols;
|
||||
const customKeyEventHandler = this.customKeyEventHandler;
|
||||
const inputHandler = this.inputHandler;
|
||||
const buffers = this.buffers;
|
||||
this.setup();
|
||||
this.customKeyEventHandler = customKeyEventHandler;
|
||||
this.inputHandler = inputHandler;
|
||||
this.buffers = buffers;
|
||||
this.refresh(0, this.rows - 1);
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
@@ -2160,19 +2185,6 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo
|
||||
el.removeEventListener(type, handler, capture);
|
||||
}
|
||||
|
||||
function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
|
||||
const thirdLevelKey =
|
||||
(browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
|
||||
(browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
|
||||
|
||||
if (ev.type === 'keypress') {
|
||||
return thirdLevelKey;
|
||||
}
|
||||
|
||||
// Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
|
||||
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
|
||||
}
|
||||
|
||||
function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
|
||||
return ev.keyCode === 16 || // Shift
|
||||
ev.keyCode === 17 || // Ctrl
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { IAttachAddonTerminal } from './Intefaces';
|
||||
import { IAttachAddonTerminal } from './Interfaces';
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
|
||||
@@ -113,8 +113,23 @@ export class SearchHelper implements ISearchHelper {
|
||||
private _findInLine(term: string, y: number): ISearchResult {
|
||||
const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase();
|
||||
const lowerTerm = term.toLowerCase();
|
||||
const searchIndex = lowerStringLine.indexOf(lowerTerm);
|
||||
let searchIndex = lowerStringLine.indexOf(lowerTerm);
|
||||
if (searchIndex >= 0) {
|
||||
const line = this._terminal.buffer.lines.get(y);
|
||||
for (let i = 0; i < searchIndex; i++) {
|
||||
const charData = line[i];
|
||||
// Adjust the searchIndex to normalize emoji into single chars
|
||||
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
|
||||
if (char.length > 1) {
|
||||
searchIndex -= char.length - 1;
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
|
||||
if (charWidth === 0) {
|
||||
searchIndex++;
|
||||
}
|
||||
}
|
||||
return {
|
||||
term,
|
||||
col: searchIndex,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/// <reference path="../../../typings/xterm.d.ts"/>
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { ITerminadoAddonTerminal } from './Intefaces';
|
||||
import { ITerminadoAddonTerminal } from './Interfaces';
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
|
||||
@@ -200,7 +200,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param color The color of the character.
|
||||
*/
|
||||
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
|
||||
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
this._ctx.font = this._getFont(terminal, false);
|
||||
this._ctx.textBaseline = 'top';
|
||||
this._clipRow(terminal, y);
|
||||
this._ctx.fillText(
|
||||
@@ -268,7 +268,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
charAtlasCellWidth,
|
||||
this._scaledCharHeight);
|
||||
} else {
|
||||
this._drawUncachedChar(terminal, char, width, fg, x, y, bold, dim);
|
||||
this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim);
|
||||
}
|
||||
// This draws the atlas (for debugging purposes)
|
||||
// this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
@@ -288,10 +288,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void {
|
||||
this._ctx.save();
|
||||
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
if (bold && terminal.options.enableBold) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
}
|
||||
this._ctx.font = this._getFont(terminal, bold);
|
||||
this._ctx.textBaseline = 'top';
|
||||
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
@@ -331,5 +328,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._scaledCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current font.
|
||||
* @param terminal The terminal.
|
||||
* @param isBold If we should use the bold fontWeight.
|
||||
*/
|
||||
protected _getFont(terminal: ITerminal, isBold: boolean): string {
|
||||
const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;
|
||||
|
||||
return `${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-119
@@ -4,13 +4,16 @@
|
||||
*/
|
||||
|
||||
import { ITerminal, ITheme, IColorSet } from '../../typings/xterm-internal';
|
||||
import { isFirefox } from '../utils/Browser';
|
||||
import { isFirefox } from '../shared/utils/Browser';
|
||||
import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator';
|
||||
|
||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
|
||||
interface ICharAtlasConfig {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: string;
|
||||
fontWeightBold: string;
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
colors: IColorSet;
|
||||
@@ -62,8 +65,28 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
|
||||
}
|
||||
}
|
||||
|
||||
const canvasFactory = (width: number, height: number) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
const charAtlasConfig: ICharAtlasRequest = {
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
background: colors.background,
|
||||
foreground: colors.foreground,
|
||||
ansiColors: colors.ansi,
|
||||
devicePixelRatio: window.devicePixelRatio
|
||||
};
|
||||
|
||||
const newEntry: ICharAtlasCacheEntry = {
|
||||
bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi),
|
||||
bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig),
|
||||
config: newConfig,
|
||||
ownedBy: [terminal]
|
||||
};
|
||||
@@ -85,6 +108,8 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.options.fontFamily,
|
||||
fontSize: terminal.options.fontSize,
|
||||
fontWeight: terminal.options.fontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold,
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
@@ -97,125 +122,10 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
|
||||
}
|
||||
return a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
a.fontWeightBold === b.fontWeightBold &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
|
||||
let generator: CharAtlasGenerator;
|
||||
|
||||
/**
|
||||
* Initializes the char atlas generator.
|
||||
* @param document The document.
|
||||
*/
|
||||
export function initialize(document: Document): void {
|
||||
if (!generator) {
|
||||
generator = new CharAtlasGenerator(document);
|
||||
}
|
||||
}
|
||||
|
||||
class CharAtlasGenerator {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _ctx: CanvasRenderingContext2D;
|
||||
|
||||
constructor(private _document: Document) {
|
||||
this._canvas = this._document.createElement('canvas');
|
||||
this._ctx = this._canvas.getContext('2d', {alpha: false});
|
||||
this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
|
||||
public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
this._canvas.width = 255 * cellWidth;
|
||||
this._canvas.height = (/*default+default bold*/2 + /*0-15*/16) * cellHeight;
|
||||
|
||||
this._ctx.fillStyle = background;
|
||||
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = foreground;
|
||||
this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
|
||||
this._ctx.textBaseline = 'top';
|
||||
|
||||
// Default color
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
|
||||
this._ctx.clip();
|
||||
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
|
||||
this._ctx.restore();
|
||||
}
|
||||
// Default color bold
|
||||
this._ctx.save();
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
|
||||
this._ctx.clip();
|
||||
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
|
||||
this._ctx.restore();
|
||||
}
|
||||
this._ctx.restore();
|
||||
|
||||
// Colors 0-15
|
||||
this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
|
||||
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
|
||||
// colors 8-15 are bold
|
||||
if (colorIndex === 8) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
}
|
||||
const y = (colorIndex + 2) * cellHeight;
|
||||
// Draw ascii characters
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this._ctx.save();
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||
this._ctx.clip();
|
||||
this._ctx.fillStyle = ansiColors[colorIndex];
|
||||
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||
this._ctx.restore();
|
||||
}
|
||||
}
|
||||
this._ctx.restore();
|
||||
|
||||
// Support is patchy for createImageBitmap at the moment, pass a canvas back
|
||||
// if support is lacking as drawImage works there too. Firefox is also
|
||||
// included here as ImageBitmap appears both buggy and has horrible
|
||||
// performance (tested on v55).
|
||||
if (!('createImageBitmap' in window) || isFirefox) {
|
||||
// Regenerate canvas and context as they are now owned by the char atlas
|
||||
const result = this._canvas;
|
||||
this._canvas = this._document.createElement('canvas');
|
||||
this._ctx = this._canvas.getContext('2d');
|
||||
this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
return result;
|
||||
}
|
||||
|
||||
const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);
|
||||
|
||||
// Remove the background color from the image so characters may overlap
|
||||
const r = parseInt(background.substr(1, 2), 16);
|
||||
const g = parseInt(background.substr(3, 2), 16);
|
||||
const b = parseInt(background.substr(5, 2), 16);
|
||||
this._clearColor(charAtlasImageData, r, g, b);
|
||||
|
||||
const promise = window.createImageBitmap(charAtlasImageData);
|
||||
// Clear the rect while the promise is in progress
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
return promise;
|
||||
}
|
||||
|
||||
private _clearColor(imageData: ImageData, r: number, g: number, b: number): void {
|
||||
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
||||
if (imageData.data[offset] === r &&
|
||||
imageData.data[offset + 1] === g &&
|
||||
imageData.data[offset + 2] === b) {
|
||||
imageData.data[offset + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
super.resize(terminal, dim, charSizeChanged);
|
||||
|
||||
// Clear the character width cache if the font or width has changed
|
||||
const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
const terminalFont = this._getFont(terminal, false);
|
||||
if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {
|
||||
this._characterWidth = dim.scaledCharWidth;
|
||||
this._characterFont = terminalFont;
|
||||
@@ -164,7 +164,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
|
||||
this._ctx.save();
|
||||
if (flags & FLAGS.BOLD) {
|
||||
this._ctx.font = `bold ${this._ctx.font}`;
|
||||
this._ctx.font = this._getFont(terminal, true);
|
||||
// Convert the FG color to the bold variant
|
||||
if (fg < 8) {
|
||||
fg += 8;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { isFirefox } from './utils/Browser';
|
||||
import { FontWeight } from './Types';
|
||||
|
||||
declare const Promise: any;
|
||||
|
||||
export interface IOffscreenCanvas {
|
||||
width: number;
|
||||
height: number;
|
||||
getContext(type: '2d', config?: Canvas2DContextAttributes): CanvasRenderingContext2D;
|
||||
transferToImageBitmap(): ImageBitmap;
|
||||
}
|
||||
|
||||
export interface ICharAtlasRequest {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
fontWeightBold: FontWeight;
|
||||
background: string;
|
||||
foreground: string;
|
||||
ansiColors: string[];
|
||||
devicePixelRatio: number;
|
||||
}
|
||||
|
||||
export const CHAR_ATLAS_CELL_SPACING = 1;
|
||||
|
||||
/**
|
||||
* Generates a char atlas.
|
||||
* @param context The window or worker context.
|
||||
* @param canvasFactory A function to generate a canvas with a width or height.
|
||||
* @param request The config for the new char atlas.
|
||||
*/
|
||||
export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, request: ICharAtlasRequest): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||
const cellWidth = request.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const cellHeight = request.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
const canvas = canvasFactory(
|
||||
/*255 ascii chars*/255 * cellWidth,
|
||||
(/*default+default bold*/2 + /*0-15*/16) * cellHeight
|
||||
);
|
||||
const ctx = canvas.getContext('2d', {alpha: false});
|
||||
|
||||
ctx.fillStyle = request.background;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = request.foreground;
|
||||
ctx.font = getFont(request.fontWeight, request);
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// Default color
|
||||
for (let i = 0; i < 256; i++) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
// Default color bold
|
||||
ctx.save();
|
||||
ctx.font = getFont(request.fontWeightBold, request);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
// Colors 0-15
|
||||
ctx.font = getFont(request.fontWeight, request);
|
||||
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
|
||||
// colors 8-15 are bold
|
||||
if (colorIndex === 8) {
|
||||
ctx.font = getFont(request.fontWeightBold, request);
|
||||
}
|
||||
const y = (colorIndex + 2) * cellHeight;
|
||||
// Draw ascii characters
|
||||
for (let i = 0; i < 256; i++) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = request.ansiColors[colorIndex];
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
// Support is patchy for createImageBitmap at the moment, pass a canvas back
|
||||
// if support is lacking as drawImage works there too. Firefox is also
|
||||
// included here as ImageBitmap appears both buggy and has horrible
|
||||
// performance (tested on v55).
|
||||
if (!('createImageBitmap' in context) || isFirefox) {
|
||||
// Don't attempt to clear background colors if createImageBitmap is not supported
|
||||
if (canvas instanceof HTMLCanvasElement) {
|
||||
// Just return the HTMLCanvas if it's a HTMLCanvasElement
|
||||
return canvas;
|
||||
} else {
|
||||
// Transfer to an ImageBitmap is this is an OffscreenCanvas
|
||||
return new Promise(r => r(canvas.transferToImageBitmap()));
|
||||
}
|
||||
}
|
||||
|
||||
const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Remove the background color from the image so characters may overlap
|
||||
const r = parseInt(request.background.substr(1, 2), 16);
|
||||
const g = parseInt(request.background.substr(3, 2), 16);
|
||||
const b = parseInt(request.background.substr(5, 2), 16);
|
||||
clearColor(charAtlasImageData, r, g, b);
|
||||
|
||||
return context.createImageBitmap(charAtlasImageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a partiicular rgb color in an ImageData completely transparent.
|
||||
*/
|
||||
function clearColor(imageData: ImageData, r: number, g: number, b: number): void {
|
||||
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
||||
if (imageData.data[offset] === r &&
|
||||
imageData.data[offset + 1] === g &&
|
||||
imageData.data[offset + 2] === b) {
|
||||
imageData.data[offset + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string {
|
||||
return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
@@ -3,8 +3,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { contains } from './Generic';
|
||||
|
||||
const isNode = (typeof navigator === 'undefined') ? true : false;
|
||||
const userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
const platform = (isNode) ? 'node' : navigator.platform;
|
||||
@@ -20,3 +18,12 @@ export const isIpad = platform === 'iPad';
|
||||
export const isIphone = platform === 'iPhone';
|
||||
export const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
|
||||
export const isLinux = platform.indexOf('Linux') >= 0;
|
||||
|
||||
/**
|
||||
* Return if the given array contains the given element
|
||||
* @param {Array} array The array to search for the given element.
|
||||
* @param {Object} el The element to look for into the array
|
||||
*/
|
||||
export function contains(arr: any[], el: any): boolean {
|
||||
return arr.indexOf(el) >= 0;
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return if the given array contains the given element
|
||||
* @param {Array} array The array to search for the given element.
|
||||
* @param {Object} el The element to look for into the array
|
||||
*/
|
||||
export function contains(arr: any[], el: any): boolean {
|
||||
return arr.indexOf(el) >= 0;
|
||||
}
|
||||
@@ -5,9 +5,8 @@
|
||||
|
||||
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ITheme, ILinkifier, IMouseHelper, ILinkMatcherOptions, LineData, IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../../typings/xterm-internal';
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper } from '../Interfaces';
|
||||
import { } from '../Types';
|
||||
import { Buffer } from '../Buffer';
|
||||
import * as Browser from './Browser';
|
||||
import * as Browser from '../shared/utils/Browser';
|
||||
|
||||
export class MockTerminal implements ITerminal {
|
||||
getOption(key: any): any {
|
||||
|
||||
Reference in New Issue
Block a user