2 * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3 * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
6 * Originally forked from (with the author's permission):
7 * Fabrice Bellard's javascript vt100 for jslinux:
8 * http://bellard.org/jslinux/
9 * Copyright (c) 2011 Fabrice Bellard
10 * The original design remains. The terminal itself
11 * has been extended to include xterm CSI codes, among
14 * Terminal Emulation References:
16 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
17 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
18 * http://invisible-island.net/vttest/
19 * http://www.inwap.com/pdp10/ansicode.txt
20 * http://linux.die.net/man/4/console_codes
21 * http://linux.die.net/man/7/urxvt
24 import { IInputHandlingTerminal, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types';
25 import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
26 import { CompositionHelper } from 'browser/input/CompositionHelper';
27 import { Viewport } from 'browser/Viewport';
28 import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
29 import { C0 } from 'common/data/EscapeSequences';
30 import { InputHandler } from './InputHandler';
31 import { Renderer } from './renderer/Renderer';
32 import { Linkifier } from 'browser/Linkifier';
33 import { SelectionService } from 'browser/services/SelectionService';
34 import * as Browser from 'common/Platform';
35 import { addDisposableDomListener } from 'browser/Lifecycle';
36 import * as Strings from 'browser/LocalizableStrings';
37 import { SoundService } from 'browser/services/SoundService';
38 import { MouseZoneManager } from 'browser/MouseZoneManager';
39 import { AccessibilityManager } from './AccessibilityManager';
40 import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
41 import { DomRenderer } from './renderer/dom/DomRenderer';
42 import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
43 import { evaluateKeyboardEvent } from 'common/input/Keyboard';
44 import { EventEmitter, IEvent } from 'common/EventEmitter';
45 import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
46 import { applyWindowsMode } from './WindowsMode';
47 import { ColorManager } from 'browser/ColorManager';
48 import { RenderService } from 'browser/services/RenderService';
49 import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
50 import { OptionsService } from 'common/services/OptionsService';
51 import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
52 import { CharSizeService } from 'browser/services/CharSizeService';
53 import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
54 import { Disposable } from 'common/Lifecycle';
55 import { IBufferSet, IBuffer } from 'common/buffer/Types';
56 import { Attributes } from 'common/buffer/Constants';
57 import { MouseService } from 'browser/services/MouseService';
58 import { IParams, IFunctionIdentifier } from 'common/parser/Types';
59 import { CoreService } from 'common/services/CoreService';
60 import { LogService } from 'common/services/LogService';
61 import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
62 import { DirtyRowService } from 'common/services/DirtyRowService';
63 import { InstantiationService } from 'common/services/InstantiationService';
64 import { CoreMouseService } from 'common/services/CoreMouseService';
65 import { WriteBuffer } from 'common/input/WriteBuffer';
67 // Let it work inside Node.js for automated testing purposes.
68 const document = (typeof window !== 'undefined') ? window.document : null;
71 export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal {
72 public textarea: HTMLTextAreaElement;
73 public element: HTMLElement;
74 public screenElement: HTMLElement;
77 * The HTMLElement that the terminal is created in, set by Terminal.open.
79 private _parent: HTMLElement | null;
80 private _document: Document;
81 private _viewportScrollArea: HTMLElement;
82 private _viewportElement: HTMLElement;
83 private _helperContainer: HTMLElement;
84 private _compositionView: HTMLElement;
86 private _visualBellTimer: number;
88 public browser: IBrowser = <any>Browser;
90 // TODO: We should remove options once components adopt optionsService
91 public get options(): ITerminalOptions { return this.optionsService.options; }
93 // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
94 public cursorState: number;
95 public cursorHidden: boolean;
97 private _customKeyEventHandler: CustomKeyEventHandler;
100 private _bufferService: IBufferService;
101 private _coreService: ICoreService;
102 private _coreMouseService: ICoreMouseService;
103 private _dirtyRowService: IDirtyRowService;
104 private _instantiationService: IInstantiationService;
105 private _logService: ILogService;
106 public optionsService: IOptionsService;
109 private _charSizeService: ICharSizeService;
110 private _mouseService: IMouseService;
111 private _renderService: IRenderService;
112 private _selectionService: ISelectionService;
113 private _soundService: ISoundService;
116 public applicationKeypad: boolean;
117 public originMode: boolean;
118 public insertMode: boolean;
119 public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false
120 public bracketedPasteMode: boolean;
123 // The current charset
124 public charset: ICharset;
125 public gcharset: number;
126 public glevel: number;
127 public charsets: ICharset[];
130 public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE;
131 public sendFocus: boolean;
134 public savedCols: number;
136 public curAttrData: IAttributeData;
137 private _eraseAttrData: IAttributeData;
139 public params: (string | number)[];
140 public currentParam: string | number;
143 private _writeBuffer: WriteBuffer;
145 // Store if user went browsing history in scrollback
146 private _userScrolling: boolean;
149 * Records whether the keydown event has already been handled and triggered a data event, if so
150 * the keypress event should not trigger a data event but should still print to the textarea so
151 * screen readers will announce it.
153 private _keyDownHandled: boolean = false;
155 private _inputHandler: InputHandler;
156 public linkifier: ILinkifier;
157 public viewport: IViewport;
158 private _compositionHelper: ICompositionHelper;
159 private _mouseZoneManager: IMouseZoneManager;
160 private _accessibilityManager: AccessibilityManager;
161 private _colorManager: ColorManager;
162 private _theme: ITheme;
163 private _windowsMode: IDisposable | undefined;
165 // bufferline to clone/copy from for new blank lines
166 private _blankLine: IBufferLine = null;
168 public get cols(): number { return this._bufferService.cols; }
169 public get rows(): number { return this._bufferService.rows; }
171 private _onCursorMove = new EventEmitter<void>();
172 public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
173 private _onData = new EventEmitter<string>();
174 public get onData(): IEvent<string> { return this._onData.event; }
175 private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
176 public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
177 private _onLineFeed = new EventEmitter<void>();
178 public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
179 private _onRender = new EventEmitter<{ start: number, end: number }>();
180 public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
181 private _onResize = new EventEmitter<{ cols: number, rows: number }>();
182 public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
183 private _onScroll = new EventEmitter<number>();
184 public get onScroll(): IEvent<number> { return this._onScroll.event; }
185 private _onSelectionChange = new EventEmitter<void>();
186 public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
187 private _onTitleChange = new EventEmitter<string>();
188 public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
190 private _onFocus = new EventEmitter<void>();
191 public get onFocus(): IEvent<void> { return this._onFocus.event; }
192 private _onBlur = new EventEmitter<void>();
193 public get onBlur(): IEvent<void> { return this._onBlur.event; }
194 public onA11yCharEmitter = new EventEmitter<string>();
195 public get onA11yChar(): IEvent<string> { return this.onA11yCharEmitter.event; }
196 public onA11yTabEmitter = new EventEmitter<number>();
197 public get onA11yTab(): IEvent<number> { return this.onA11yTabEmitter.event; }
200 * Creates a new `Terminal` object.
202 * @param options An object containing a set of options, the available options are:
203 * - `cursorBlink` (boolean): Whether the terminal cursor blinks
204 * - `cols` (number): The number of columns of the terminal (horizontal size)
205 * - `rows` (number): The number of rows of the terminal (vertical size)
209 * @alias module:xterm/src/xterm
212 options: ITerminalOptions = {}
216 // Setup and initialize common services
217 this._instantiationService = new InstantiationService();
218 this.optionsService = new OptionsService(options);
219 this._instantiationService.setService(IOptionsService, this.optionsService);
220 this._bufferService = this._instantiationService.createInstance(BufferService);
221 this._instantiationService.setService(IBufferService, this._bufferService);
222 this._logService = this._instantiationService.createInstance(LogService);
223 this._instantiationService.setService(ILogService, this._logService);
224 this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
225 this._instantiationService.setService(ICoreService, this._coreService);
226 this._coreService.onData(e => this._onData.fire(e));
227 this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
228 this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
229 this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
230 this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
232 this._setupOptionsListeners();
235 this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data));
238 public dispose(): void {
239 if (this._isDisposed) {
243 if (this._windowsMode) {
244 this._windowsMode.dispose();
245 this._windowsMode = undefined;
247 if (this._renderService) {
248 this._renderService.dispose();
250 this._customKeyEventHandler = null;
251 this.write = () => {};
252 if (this.element && this.element.parentNode) {
253 this.element.parentNode.removeChild(this.element);
257 private _setup(): void {
258 this._parent = document ? document.body : null;
260 this.cursorState = 0;
261 this.cursorHidden = false;
262 this._customKeyEventHandler = null;
265 this.applicationKeypad = false;
266 this.originMode = false;
267 this.insertMode = false;
268 this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
269 this.bracketedPasteMode = false;
273 this.gcharset = null;
275 // TODO: Can this be just []?
276 this.charsets = [null];
278 this.curAttrData = DEFAULT_ATTR_DATA.clone();
279 this._eraseAttrData = DEFAULT_ATTR_DATA.clone();
282 this.currentParam = 0;
284 this._userScrolling = false;
286 // Register input handler and refire/handle events
287 this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService);
288 this._inputHandler.onCursorMove(() => this._onCursorMove.fire());
289 this._inputHandler.onLineFeed(() => this._onLineFeed.fire());
290 this.register(this._inputHandler);
292 this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
294 if (this.options.windowsMode) {
295 this._windowsMode = applyWindowsMode(this);
300 * Convenience property to active buffer.
302 public get buffer(): IBuffer {
303 return this.buffers.active;
306 public get buffers(): IBufferSet {
307 return this._bufferService.buffers;
311 * back_color_erase feature for xterm.
313 public eraseAttrData(): IAttributeData {
314 this._eraseAttrData.bg &= ~(Attributes.CM_MASK | 0xFFFFFF);
315 this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000;
316 return this._eraseAttrData;
320 * Focus the terminal. Delegates focus handling to the terminal's DOM element.
322 public focus(): void {
324 this.textarea.focus({ preventScroll: true });
328 public get isFocused(): boolean {
329 return document.activeElement === this.textarea && document.hasFocus();
332 private _setupOptionsListeners(): void {
333 // TODO: These listeners should be owned by individual components
334 this.optionsService.onOptionChange(key => {
338 // When the font changes the size of the cells may change which requires a renderer clear
339 if (this._renderService) {
340 this._renderService.clear();
342 if (this._charSizeService) {
343 this._charSizeService.measure();
346 case 'drawBoldTextInBrightColors':
347 case 'letterSpacing':
350 case 'fontWeightBold':
351 // When the font changes the size of the cells may change which requires a renderer clear
352 if (this._renderService) {
353 this._renderService.clear();
354 this._renderService.onResize(this.cols, this.rows);
355 this.refresh(0, this.rows - 1);
359 if (this._renderService) {
360 this._renderService.setRenderer(this._createRenderer());
361 this._renderService.onResize(this.cols, this.rows);
365 this.buffers.resize(this.cols, this.rows);
367 this.viewport.syncScrollArea();
370 case 'screenReaderMode':
371 if (this.optionsService.options.screenReaderMode) {
372 if (!this._accessibilityManager && this._renderService) {
373 this._accessibilityManager = new AccessibilityManager(this, this._renderService);
376 if (this._accessibilityManager) {
377 this._accessibilityManager.dispose();
378 this._accessibilityManager = null;
382 case 'tabStopWidth': this.buffers.setupTabStops(); break;
384 this._setTheme(this.optionsService.options.theme);
387 if (this.optionsService.options.windowsMode) {
388 if (!this._windowsMode) {
389 this._windowsMode = applyWindowsMode(this);
392 if (this._windowsMode) {
393 this._windowsMode.dispose();
394 this._windowsMode = undefined;
403 * Binds the desired focus behavior on a given terminal object.
405 private _onTextAreaFocus(ev: KeyboardEvent): void {
406 if (this.sendFocus) {
407 this._coreService.triggerDataEvent(C0.ESC + '[I');
409 this.updateCursorStyle(ev);
410 this.element.classList.add('focus');
412 this._onFocus.fire();
416 * Blur the terminal, calling the blur function on the terminal's underlying
419 public blur(): void {
420 return this.textarea.blur();
424 * Binds the desired blur behavior on a given terminal object.
426 private _onTextAreaBlur(): void {
427 // Text can safely be removed on blur. Doing it earlier could interfere with
428 // screen readers reading it out.
429 this.textarea.value = '';
430 this.refresh(this.buffer.y, this.buffer.y);
431 if (this.sendFocus) {
432 this._coreService.triggerDataEvent(C0.ESC + '[O');
434 this.element.classList.remove('focus');
439 * Initialize default behavior
441 private _initGlobal(): void {
444 // Bind clipboard functionality
445 this.register(addDisposableDomListener(this.element, 'copy', (event: ClipboardEvent) => {
446 // If mouse events are active it means the selection manager is disabled and
447 // copy should be handled by the host program.
448 if (!this.hasSelection()) {
451 copyHandler(event, this._selectionService);
453 const pasteHandlerWrapper = (event: ClipboardEvent) => handlePasteEvent(event, this.textarea, this.bracketedPasteMode, this._coreService);
454 this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper));
455 this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper));
457 // Handle right click context menus
458 if (Browser.isFirefox) {
459 // Firefox doesn't appear to fire the contextmenu event on right click
460 this.register(addDisposableDomListener(this.element, 'mousedown', (event: MouseEvent) => {
461 if (event.button === 2) {
462 rightClickHandler(event, this.textarea, this.screenElement, this._selectionService, this.options.rightClickSelectsWord);
466 this.register(addDisposableDomListener(this.element, 'contextmenu', (event: MouseEvent) => {
467 rightClickHandler(event, this.textarea, this.screenElement, this._selectionService, this.options.rightClickSelectsWord);
471 // Move the textarea under the cursor when middle clicking on Linux to ensure
472 // middle click to paste selection works. This only appears to work in Chrome
473 // at the time is writing.
474 if (Browser.isLinux) {
475 // Use auxclick event over mousedown the latter doesn't seem to work. Note
476 // that the regular click event doesn't fire for the middle mouse button.
477 this.register(addDisposableDomListener(this.element, 'auxclick', (event: MouseEvent) => {
478 if (event.button === 1) {
479 moveTextAreaUnderMouseCursor(event, this.textarea, this.screenElement);
486 * Apply key handling to the terminal
488 private _bindKeys(): void {
489 this.register(addDisposableDomListener(this.textarea, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true));
490 this.register(addDisposableDomListener(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));
491 this.register(addDisposableDomListener(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));
492 this.register(addDisposableDomListener(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart()));
493 this.register(addDisposableDomListener(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e)));
494 this.register(addDisposableDomListener(this.textarea, 'compositionend', () => this._compositionHelper.compositionend()));
495 this.register(this.onRender(() => this._compositionHelper.updateCompositionElements()));
496 this.register(this.onRender(e => this._queueLinkification(e.start, e.end)));
500 * Opens the terminal within an element.
502 * @param parent The element to create the terminal within.
504 public open(parent: HTMLElement): void {
505 this._parent = parent || this._parent;
508 throw new Error('Terminal requires a parent element.');
511 if (!document.body.contains(parent)) {
512 this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
515 this._document = this._parent.ownerDocument;
517 // Create main element container
518 this.element = this._document.createElement('div');
519 this.element.dir = 'ltr'; // xterm.css assumes LTR
520 this.element.classList.add('terminal');
521 this.element.classList.add('xterm');
522 this.element.setAttribute('tabindex', '0');
523 this._parent.appendChild(this.element);
525 // Performance: Use a document fragment to build the terminal
526 // viewport and helper elements detached from the DOM
527 const fragment = document.createDocumentFragment();
528 this._viewportElement = document.createElement('div');
529 this._viewportElement.classList.add('xterm-viewport');
530 fragment.appendChild(this._viewportElement);
531 this._viewportScrollArea = document.createElement('div');
532 this._viewportScrollArea.classList.add('xterm-scroll-area');
533 this._viewportElement.appendChild(this._viewportScrollArea);
535 this.screenElement = document.createElement('div');
536 this.screenElement.classList.add('xterm-screen');
537 // Create the container that will hold helpers like the textarea for
538 // capturing DOM Events. Then produce the helpers.
539 this._helperContainer = document.createElement('div');
540 this._helperContainer.classList.add('xterm-helpers');
541 this.screenElement.appendChild(this._helperContainer);
542 fragment.appendChild(this.screenElement);
544 this.textarea = document.createElement('textarea');
545 this.textarea.classList.add('xterm-helper-textarea');
546 this.textarea.setAttribute('aria-label', Strings.promptLabel);
547 this.textarea.setAttribute('aria-multiline', 'false');
548 this.textarea.setAttribute('autocorrect', 'off');
549 this.textarea.setAttribute('autocapitalize', 'off');
550 this.textarea.setAttribute('spellcheck', 'false');
551 this.textarea.tabIndex = 0;
552 this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._onTextAreaFocus(ev)));
553 this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
554 this._helperContainer.appendChild(this.textarea);
556 this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
557 this._instantiationService.setService(ICharSizeService, this._charSizeService);
559 this._compositionView = document.createElement('div');
560 this._compositionView.classList.add('composition-view');
561 this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
562 this._helperContainer.appendChild(this._compositionView);
564 // Performance: Add viewport and helper elements from the fragment
565 this.element.appendChild(fragment);
567 this._theme = this.options.theme || this._theme;
568 this.options.theme = undefined;
569 this._colorManager = new ColorManager(document, this.options.allowTransparency);
570 this._colorManager.setTheme(this._theme);
572 const renderer = this._createRenderer();
573 this._renderService = this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement);
574 this._instantiationService.setService(IRenderService, this._renderService);
575 this._renderService.onRender(e => this._onRender.fire(e));
576 this.onResize(e => this._renderService.resize(e.cols, e.rows));
578 this._soundService = this._instantiationService.createInstance(SoundService);
579 this._instantiationService.setService(ISoundService, this._soundService);
580 this._mouseService = this._instantiationService.createInstance(MouseService);
581 this._instantiationService.setService(IMouseService, this._mouseService);
583 this.viewport = this._instantiationService.createInstance(Viewport,
584 (amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
585 this._viewportElement,
586 this._viewportScrollArea
588 this.viewport.onThemeChange(this._colorManager.colors);
589 this.register(this.viewport);
591 this.register(this.onCursorMove(() => this._renderService.onCursorMove()));
592 this.register(this.onResize(() => this._renderService.onResize(this.cols, this.rows)));
593 this.register(this.onBlur(() => this._renderService.onBlur()));
594 this.register(this.onFocus(() => this._renderService.onFocus()));
595 this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea()));
597 this._selectionService = this._instantiationService.createInstance(SelectionService,
598 (amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
601 this._instantiationService.setService(ISelectionService, this._selectionService);
602 this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
603 this.register(this._selectionService.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
604 this.register(this._selectionService.onLinuxMouseSelection(text => {
605 // If there's a new selection, put it into the textarea, focus and select it
606 // in order to register it as a selection on the OS. This event is fired
607 // only on Linux to enable middle click to paste selection.
608 this.textarea.value = text;
609 this.textarea.focus();
610 this.textarea.select();
612 this.register(this.onScroll(() => {
613 this.viewport.syncScrollArea();
614 this._selectionService.refresh();
616 this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService.refresh()));
618 this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement);
619 this.register(this._mouseZoneManager);
620 this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
621 this.linkifier.attachToDom(this.element, this._mouseZoneManager);
623 // This event listener must be registered aftre MouseZoneManager is created
624 this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
626 // apply mouse event classes set by escape codes before terminal was attached
627 if (this.mouseEvents) {
628 this._selectionService.disable();
629 this.element.classList.add('enable-mouse-events');
631 this._selectionService.enable();
634 if (this.options.screenReaderMode) {
635 // Note that this must be done *after* the renderer is created in order to
636 // ensure the correct order of the dprchange event
637 this._accessibilityManager = new AccessibilityManager(this, this._renderService);
640 // Measure the character size
641 this._charSizeService.measure();
643 // Setup loop that draws to screen
644 this.refresh(0, this.rows - 1);
646 // Initialize global actions that need to be taken on the document.
649 // Listen for mouse events and translate
650 // them into terminal mouse protocols.
654 private _createRenderer(): IRenderer {
655 switch (this.options.rendererType) {
656 case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService);
657 case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService);
658 default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
663 * Sets the theme on the renderer. The renderer must have been initialized.
664 * @param theme The theme to set.
666 private _setTheme(theme: ITheme): void {
668 if (this._colorManager) {
669 this._colorManager.setTheme(theme);
671 if (this._renderService) {
672 this._renderService.setColors(this._colorManager.colors);
675 this.viewport.onThemeChange(this._colorManager.colors);
680 * Bind certain mouse events to the terminal.
681 * By default only 3 button + wheel up/down is ativated. For higher buttons
682 * no mouse report will be created. Typically the standard actions will be active.
684 * There are several reasons not to enable support for higher buttons/wheel:
685 * - Button 4 and 5 are typically used for history back and forward navigation,
686 * there is no straight forward way to supress/intercept those standard actions.
687 * - Support for higher buttons does not work in some platform/browser combinations.
688 * - Left/right wheel was not tested.
689 * - Emulators vary in mouse button support, typically only 3 buttons and
690 * wheel up/down work reliable.
692 * TODO: Move mouse event code into its own file.
694 public bindMouse(): void {
696 const el = this.element;
698 // send event to CoreMouseService
699 function sendEvent(ev: MouseEvent | WheelEvent): boolean {
702 // get mouse coordinates
703 pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
708 let but: CoreMouseButton;
709 let action: CoreMouseAction;
710 switch ((<any>ev).overrideType || ev.type) {
712 action = CoreMouseAction.MOVE;
713 if (ev.buttons === undefined) {
714 // buttons is not supported on macOS, try to get a value from button instead
715 but = CoreMouseButton.NONE;
716 if (ev.button !== undefined) {
717 but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
720 // according to MDN buttons only reports up to button 5 (AUX2)
721 but = ev.buttons & 1 ? CoreMouseButton.LEFT :
722 ev.buttons & 4 ? CoreMouseButton.MIDDLE :
723 ev.buttons & 2 ? CoreMouseButton.RIGHT :
724 CoreMouseButton.NONE; // fallback to NONE
728 action = CoreMouseAction.UP;
729 but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
732 action = CoreMouseAction.DOWN;
733 but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
736 // only UP/DOWN wheel events are respected
737 if ((ev as WheelEvent).deltaY !== 0) {
738 action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
740 but = CoreMouseButton.WHEEL;
743 // dont handle other event types by accident
747 // exit if we cannot determine valid button/action values
748 // do nothing for higher buttons than wheel
749 if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
753 return self._coreMouseService.triggerMouseEvent({
754 col: pos.x - 33, // FIXME: why -33 here?
765 * Event listener state handling.
766 * We listen to the onProtocolChange event of CoreMouseService and put
767 * requested listeners in `requestedEvents`. With this the listeners
768 * have all bits to do the event listener juggling.
769 * Note: 'mousedown' currently is "always on" and not managed
770 * by onProtocolChange.
772 const requestedEvents: {[key: string]: ((ev: Event) => void) | null} = {
778 const eventListeners: {[key: string]: (ev: Event) => void} = {
779 mouseup: (ev: MouseEvent) => {
782 // if no other button is held remove global handlers
783 this._document.removeEventListener('mouseup', requestedEvents.mouseup);
784 if (requestedEvents.mousedrag) {
785 this._document.removeEventListener('mousemove', requestedEvents.mousedrag);
788 return this.cancel(ev);
790 wheel: (ev: WheelEvent) => {
793 return this.cancel(ev);
795 mousedrag: (ev: MouseEvent) => {
796 // deal only with move while a button is held
801 mousemove: (ev: MouseEvent) => {
802 // deal only with move without any button
808 this._coreMouseService.onProtocolChange(events => {
809 // apply global changes on events
810 this.mouseEvents = events;
812 if (this.optionsService.options.logLevel === 'debug') {
813 this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events));
815 this.element.classList.add('enable-mouse-events');
816 this._selectionService.disable();
818 this._logService.debug('Unbinding from mouse events.');
819 this.element.classList.remove('enable-mouse-events');
820 this._selectionService.enable();
823 // add/remove handlers from requestedEvents
825 if (!(events & CoreMouseEventType.MOVE)) {
826 el.removeEventListener('mousemove', requestedEvents.mousemove);
827 requestedEvents.mousemove = null;
828 } else if (!requestedEvents.mousemove) {
829 el.addEventListener('mousemove', eventListeners.mousemove);
830 requestedEvents.mousemove = eventListeners.mousemove;
833 if (!(events & CoreMouseEventType.WHEEL)) {
834 el.removeEventListener('wheel', requestedEvents.wheel);
835 requestedEvents.wheel = null;
836 } else if (!requestedEvents.wheel) {
837 el.addEventListener('wheel', eventListeners.wheel);
838 requestedEvents.wheel = eventListeners.wheel;
841 if (!(events & CoreMouseEventType.UP)) {
842 this._document.removeEventListener('mouseup', requestedEvents.mouseup);
843 requestedEvents.mouseup = null;
844 } else if (!requestedEvents.mouseup) {
845 requestedEvents.mouseup = eventListeners.mouseup;
848 if (!(events & CoreMouseEventType.DRAG)) {
849 this._document.removeEventListener('mousemove', requestedEvents.mousedrag);
850 requestedEvents.mousedrag = null;
851 } else if (!requestedEvents.mousedrag) {
852 requestedEvents.mousedrag = eventListeners.mousedrag;
855 // force initial onProtocolChange so we dont miss early mouse requests
856 this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol;
859 * "Always on" event listeners.
861 this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => {
865 // Don't send the mouse button to the pty if mouse events are disabled or
866 // if the selection manager is having selection forced (ie. a modifier is
868 if (!this.mouseEvents || this._selectionService.shouldForceSelection(ev)) {
874 // Register additional global handlers which should keep reporting outside
875 // of the terminal element.
876 // Note: Other emulators also do this for 'mousedown' while a button
877 // is held, we currently limit 'mousedown' to the terminal only.
878 if (requestedEvents.mouseup) {
879 this._document.addEventListener('mouseup', requestedEvents.mouseup);
881 if (requestedEvents.mousedrag) {
882 this._document.addEventListener('mousemove', requestedEvents.mousedrag);
885 return this.cancel(ev);
888 this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
889 if (!requestedEvents.wheel) {
890 // Convert wheel events into up/down events when the buffer does not have scrollback, this
891 // enables scrolling in apps hosted in the alt buffer such as vim or tmux.
892 if (!this.buffer.hasScrollback) {
893 const amount = this.viewport.getLinesScrolled(ev);
895 // Do nothing if there's no vertical scroll
900 // Construct and send sequences
901 const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
903 for (let i = 0; i < Math.abs(amount); i++) {
906 this._coreService.triggerDataEvent(data, true);
912 // allow wheel scrolling in
913 // the shell for example
914 this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
915 if (requestedEvents.wheel) return;
916 if (!this.viewport.onWheel(ev)) {
917 return this.cancel(ev);
921 this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
922 if (this.mouseEvents) return;
923 this.viewport.onTouchStart(ev);
924 return this.cancel(ev);
927 this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
928 if (this.mouseEvents) return;
929 if (!this.viewport.onTouchMove(ev)) {
930 return this.cancel(ev);
937 * Tells the renderer to refresh terminal content between two rows (inclusive) at the next
939 * @param start The row to start from (between 0 and this.rows - 1).
940 * @param end The row to end at (between start and this.rows - 1).
942 public refresh(start: number, end: number): void {
943 if (this._renderService) {
944 this._renderService.refreshRows(start, end);
949 * Queues linkification for the specified rows.
950 * @param start The row to start from (between 0 and this.rows - 1).
951 * @param end The row to end at (between start and this.rows - 1).
953 private _queueLinkification(start: number, end: number): void {
954 if (this.linkifier) {
955 this.linkifier.linkifyRows(start, end);
960 * Change the cursor style for different selection modes
962 public updateCursorStyle(ev: KeyboardEvent): void {
963 if (this._selectionService && this._selectionService.shouldColumnSelect(ev)) {
964 this.element.classList.add('column-select');
966 this.element.classList.remove('column-select');
971 * Display the cursor element
973 public showCursor(): void {
974 if (!this.cursorState) {
975 this.cursorState = 1;
976 this.refresh(this.buffer.y, this.buffer.y);
981 * Scroll the terminal down 1 row, creating a blank line.
982 * @param isWrapped Whether the new line is wrapped from the previous line.
984 public scroll(isWrapped: boolean = false): void {
985 let newLine: IBufferLine;
986 newLine = this._blankLine;
987 const eraseAttr = this.eraseAttrData();
988 if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {
989 newLine = this.buffer.getBlankLine(eraseAttr, isWrapped);
990 this._blankLine = newLine;
992 newLine.isWrapped = isWrapped;
994 const topRow = this.buffer.ybase + this.buffer.scrollTop;
995 const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;
997 if (this.buffer.scrollTop === 0) {
998 // Determine whether the buffer is going to be trimmed after insertion.
999 const willBufferBeTrimmed = this.buffer.lines.isFull;
1001 // Insert the line using the fastest method
1002 if (bottomRow === this.buffer.lines.length - 1) {
1003 if (willBufferBeTrimmed) {
1004 this.buffer.lines.recycle().copyFrom(newLine);
1006 this.buffer.lines.push(newLine.clone());
1009 this.buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
1012 // Only adjust ybase and ydisp when the buffer is not trimmed
1013 if (!willBufferBeTrimmed) {
1014 this.buffer.ybase++;
1015 // Only scroll the ydisp with ybase if the user has not scrolled up
1016 if (!this._userScrolling) {
1017 this.buffer.ydisp++;
1020 // When the buffer is full and the user has scrolled up, keep the text
1021 // stable unless ydisp is right at the top
1022 if (this._userScrolling) {
1023 this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);
1027 // scrollTop is non-zero which means no line will be going to the
1028 // scrollback, instead we can just shift them in-place.
1029 const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
1030 this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
1031 this.buffer.lines.set(bottomRow, newLine.clone());
1034 // Move the viewport to the bottom of the buffer unless the user is
1036 if (!this._userScrolling) {
1037 this.buffer.ydisp = this.buffer.ybase;
1040 // Flag rows that need updating
1041 this._dirtyRowService.markRangeDirty(this.buffer.scrollTop, this.buffer.scrollBottom);
1043 this._onScroll.fire(this.buffer.ydisp);
1047 * Scroll the display of the terminal
1048 * @param disp The number of lines to scroll down (negative scroll up).
1049 * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
1050 * to avoid unwanted events being handled by the viewport when the event was triggered from the
1051 * viewport originally.
1053 public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
1055 if (this.buffer.ydisp === 0) {
1058 this._userScrolling = true;
1059 } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
1060 this._userScrolling = false;
1063 const oldYdisp = this.buffer.ydisp;
1064 this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);
1066 // No change occurred, don't trigger scroll/refresh
1067 if (oldYdisp === this.buffer.ydisp) {
1071 if (!suppressScrollEvent) {
1072 this._onScroll.fire(this.buffer.ydisp);
1075 this.refresh(0, this.rows - 1);
1079 * Scroll the display of the terminal by a number of pages.
1080 * @param pageCount The number of pages to scroll (negative scrolls up).
1082 public scrollPages(pageCount: number): void {
1083 this.scrollLines(pageCount * (this.rows - 1));
1087 * Scrolls the display of the terminal to the top.
1089 public scrollToTop(): void {
1090 this.scrollLines(-this.buffer.ydisp);
1094 * Scrolls the display of the terminal to the bottom.
1096 public scrollToBottom(): void {
1097 this.scrollLines(this.buffer.ybase - this.buffer.ydisp);
1100 public scrollToLine(line: number): void {
1101 const scrollAmount = line - this.buffer.ydisp;
1102 if (scrollAmount !== 0) {
1103 this.scrollLines(scrollAmount);
1107 public paste(data: string): void {
1108 paste(data, this.textarea, this.bracketedPasteMode, this._coreService);
1112 * Attaches a custom key event handler which is run before keys are processed,
1113 * giving consumers of xterm.js ultimate control as to what keys should be
1114 * processed by the terminal and what keys should not.
1115 * @param customKeyEventHandler The custom KeyboardEvent handler to attach.
1116 * This is a function that takes a KeyboardEvent, allowing consumers to stop
1117 * propagation and/or prevent the default action. The function returns whether
1118 * the event should be processed by xterm.js.
1120 public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
1121 this._customKeyEventHandler = customKeyEventHandler;
1124 /** Add handler for ESC escape sequence. See xterm.d.ts for details. */
1125 public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable {
1126 return this._inputHandler.addEscHandler(id, callback);
1129 /** Add handler for DCS escape sequence. See xterm.d.ts for details. */
1130 public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
1131 return this._inputHandler.addDcsHandler(id, callback);
1134 /** Add handler for CSI escape sequence. See xterm.d.ts for details. */
1135 public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
1136 return this._inputHandler.addCsiHandler(id, callback);
1138 /** Add handler for OSC escape sequence. See xterm.d.ts for details. */
1139 public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
1140 return this._inputHandler.addOscHandler(ident, callback);
1144 * Registers a link matcher, allowing custom link patterns to be matched and
1146 * @param regex The regular expression to search for, specifically
1147 * this searches the textContent of the rows. You will want to use \s to match
1148 * a space ' ' character for example.
1149 * @param handler The callback when the link is called.
1150 * @param options Options for the link matcher.
1151 * @return The ID of the new matcher, this can be used to deregister.
1153 public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {
1154 const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
1155 this.refresh(0, this.rows - 1);
1160 * Deregisters a link matcher if it has been registered.
1161 * @param matcherId The link matcher's ID (returned after register)
1163 public deregisterLinkMatcher(matcherId: number): void {
1164 if (this.linkifier.deregisterLinkMatcher(matcherId)) {
1165 this.refresh(0, this.rows - 1);
1169 public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
1170 const joinerId = this._renderService.registerCharacterJoiner(handler);
1171 this.refresh(0, this.rows - 1);
1175 public deregisterCharacterJoiner(joinerId: number): void {
1176 if (this._renderService.deregisterCharacterJoiner(joinerId)) {
1177 this.refresh(0, this.rows - 1);
1181 public get markers(): IMarker[] {
1182 return this.buffer.markers;
1185 public addMarker(cursorYOffset: number): IMarker {
1186 // Disallow markers on the alt buffer
1187 if (this.buffer !== this.buffers.normal) {
1191 return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
1195 * Gets whether the terminal has an active selection.
1197 public hasSelection(): boolean {
1198 return this._selectionService ? this._selectionService.hasSelection : false;
1202 * Selects text within the terminal.
1203 * @param column The column the selection starts at..
1204 * @param row The row the selection starts at.
1205 * @param length The length of the selection.
1207 public select(column: number, row: number, length: number): void {
1208 this._selectionService.setSelection(column, row, length);
1212 * Gets the terminal's current selection, this is useful for implementing copy
1213 * behavior outside of xterm.js.
1215 public getSelection(): string {
1216 return this._selectionService ? this._selectionService.selectionText : '';
1219 public getSelectionPosition(): ISelectionPosition | undefined {
1220 if (!this._selectionService.hasSelection) {
1225 startColumn: this._selectionService.selectionStart[0],
1226 startRow: this._selectionService.selectionStart[1],
1227 endColumn: this._selectionService.selectionEnd[0],
1228 endRow: this._selectionService.selectionEnd[1]
1233 * Clears the current terminal selection.
1235 public clearSelection(): void {
1236 if (this._selectionService) {
1237 this._selectionService.clearSelection();
1242 * Selects all text within the terminal.
1244 public selectAll(): void {
1245 if (this._selectionService) {
1246 this._selectionService.selectAll();
1250 public selectLines(start: number, end: number): void {
1251 if (this._selectionService) {
1252 this._selectionService.selectLines(start, end);
1257 * Handle a keydown event
1259 * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
1260 * @param ev The keydown event to be handled.
1262 protected _keyDown(event: KeyboardEvent): boolean {
1263 this._keyDownHandled = false;
1265 if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
1269 if (!this._compositionHelper.keydown(event)) {
1270 if (this.buffer.ybase !== this.buffer.ydisp) {
1271 this.scrollToBottom();
1276 const result = evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
1278 this.updateCursorStyle(event);
1280 if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {
1281 const scrollCount = this.rows - 1;
1282 this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);
1283 return this.cancel(event, true);
1286 if (result.type === KeyboardResultType.SELECT_ALL) {
1290 if (this._isThirdLevelShift(this.browser, event)) {
1294 if (result.cancel) {
1295 // The event is canceled at the end already, is this necessary?
1296 this.cancel(event, true);
1303 // If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
1304 // will announce deleted characters. This will not work 100% of the time but it should cover
1306 if (result.key === C0.ETX || result.key === C0.CR) {
1307 this.textarea.value = '';
1310 this._onKey.fire({ key: result.key, domEvent: event });
1312 this._coreService.triggerDataEvent(result.key, true);
1314 // Cancel events when not in screen reader mode so events don't get bubbled up and handled by
1315 // other listeners. When screen reader mode is enabled, this could cause issues if the event
1316 // is handled at a higher level, this is a compromise in order to echo keys to the screen
1318 if (!this.optionsService.options.screenReaderMode) {
1319 return this.cancel(event, true);
1322 this._keyDownHandled = true;
1325 private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {
1326 const thirdLevelKey =
1327 (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
1328 (browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
1330 if (ev.type === 'keypress') {
1331 return thirdLevelKey;
1334 // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
1335 return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
1339 * Set the G level of the terminal
1342 public setgLevel(g: number): void {
1344 this.charset = this.charsets[g];
1348 * Set the charset for the given G level of the terminal
1352 public setgCharset(g: number, charset: ICharset): void {
1353 this.charsets[g] = charset;
1354 if (this.glevel === g) {
1355 this.charset = charset;
1359 protected _keyUp(ev: KeyboardEvent): void {
1360 if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
1364 if (!wasModifierKeyOnlyEvent(ev)) {
1368 this.updateCursorStyle(ev);
1372 * Handle a keypress event.
1374 * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
1375 * @param ev The keypress event to be handled.
1377 protected _keyPress(ev: KeyboardEvent): boolean {
1380 if (this._keyDownHandled) {
1384 if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
1392 } else if (ev.which === null || ev.which === undefined) {
1394 } else if (ev.which !== 0 && ev.charCode !== 0) {
1401 (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)
1406 key = String.fromCharCode(key);
1408 this._onKey.fire({ key, domEvent: ev });
1410 this._coreService.triggerDataEvent(key, true);
1417 * Note: We could do sweet things with webaudio here
1419 public bell(): void {
1420 if (this._soundBell()) {
1421 this._soundService.playBellSound();
1424 if (this._visualBell()) {
1425 this.element.classList.add('visual-bell-active');
1426 clearTimeout(this._visualBellTimer);
1427 this._visualBellTimer = window.setTimeout(() => {
1428 this.element.classList.remove('visual-bell-active');
1434 * Resizes the terminal.
1436 * @param x The number of columns to resize to.
1437 * @param y The number of rows to resize to.
1439 public resize(x: number, y: number): void {
1440 if (isNaN(x) || isNaN(y)) {
1444 if (x === this.cols && y === this.rows) {
1445 // Check if we still need to measure the char size (fixes #785).
1446 if (this._charSizeService && !this._charSizeService.hasValidSize) {
1447 this._charSizeService.measure();
1452 if (x < MINIMUM_COLS) x = MINIMUM_COLS;
1453 if (y < MINIMUM_ROWS) y = MINIMUM_ROWS;
1455 this.buffers.resize(x, y);
1457 this._bufferService.resize(x, y);
1458 this.buffers.setupTabStops(this.cols);
1460 if (this._charSizeService) {
1461 this._charSizeService.measure();
1464 // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
1466 this.viewport.syncScrollArea(true);
1468 this.refresh(0, this.rows - 1);
1469 this._onResize.fire({ cols: x, rows: y });
1473 * Clear the entire buffer, making the prompt line the new first line.
1475 public clear(): void {
1476 if (this.buffer.ybase === 0 && this.buffer.y === 0) {
1477 // Don't clear if it's already clear
1480 this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));
1481 this.buffer.lines.length = 1;
1482 this.buffer.ydisp = 0;
1483 this.buffer.ybase = 0;
1485 for (let i = 1; i < this.rows; i++) {
1486 this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));
1488 this.refresh(0, this.rows - 1);
1489 this._onScroll.fire(this.buffer.ydisp);
1493 * Evaluate if the current terminal is the given argument.
1494 * @param term The terminal name to evaluate
1496 public is(term: string): boolean {
1497 return (this.options.termName + '').indexOf(term) === 0;
1501 * Emit the data event and populate the given data.
1502 * @param data The data to populate in the event.
1504 // public handler(data: string): void {
1505 // // Prevents all events to pty process if stdin is disabled
1506 // if (this.options.disableStdin) {
1510 // // Clear the selection if the selection manager is available and has an active selection
1511 // if (this.selectionService && this.selectionService.hasSelection) {
1512 // this.selectionService.clearSelection();
1515 // // Input is being sent to the terminal, the terminal should focus the prompt.
1516 // if (this.buffer.ybase !== this.buffer.ydisp) {
1517 // this.scrollToBottom();
1519 // this._onData.fire(data);
1523 * Emit the 'title' event and populate the given title.
1524 * @param title The title to populate in the event.
1526 public handleTitle(title: string): void {
1527 this._onTitleChange.fire(title);
1532 * Note: Calling this directly from JS is synchronous but does not clear
1533 * input buffers and does not reset the parser, thus the terminal will
1534 * continue to apply pending input data.
1535 * If you need in band reset (synchronous with input data) consider
1536 * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).
1538 public reset(): void {
1540 * Since _setup handles a full terminal creation, we have to carry forward
1541 * a few things that should not reset.
1543 this.options.rows = this.rows;
1544 this.options.cols = this.cols;
1545 const customKeyEventHandler = this._customKeyEventHandler;
1546 const inputHandler = this._inputHandler;
1547 const cursorState = this.cursorState;
1548 const userScrolling = this._userScrolling;
1551 this._bufferService.reset();
1552 this._coreService.reset();
1553 this._coreMouseService.reset();
1554 if (this._selectionService) {
1555 this._selectionService.reset();
1559 this._customKeyEventHandler = customKeyEventHandler;
1560 this._inputHandler = inputHandler;
1561 this.cursorState = cursorState;
1562 this._userScrolling = userScrolling;
1564 // do a full screen refresh
1565 this.refresh(0, this.rows - 1);
1566 if (this.viewport) {
1567 this.viewport.syncScrollArea();
1571 // TODO: Remove cancel function and cancelEvents option
1572 public cancel(ev: Event, force?: boolean): boolean {
1573 if (!this.options.cancelEvents && !force) {
1576 ev.preventDefault();
1577 ev.stopPropagation();
1581 private _visualBell(): boolean {
1583 // return this.options.bellStyle === 'visual' ||
1584 // this.options.bellStyle === 'both';
1587 private _soundBell(): boolean {
1588 return this.options.bellStyle === 'sound';
1589 // return this.options.bellStyle === 'sound' ||
1590 // this.options.bellStyle === 'both';
1593 public write(data: string | Uint8Array, callback?: () => void): void {
1594 this._writeBuffer.write(data, callback);
1597 public writeSync(data: string | Uint8Array): void {
1598 this._writeBuffer.writeSync(data);
1606 function wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
1607 return ev.keyCode === 16 || // Shift
1608 ev.keyCode === 17 || // Ctrl
1609 ev.keyCode === 18; // Alt