` container so that the css will still apply.\n this._debugRootContainer = document.createElement('div');\n this._debugRootContainer.classList.add('xterm');\n\n this._debugRootContainer.appendChild(document.createTextNode('------start a11y------'));\n this._debugRootContainer.appendChild(this._accessibilityContainer);\n this._debugRootContainer.appendChild(document.createTextNode('------end a11y------'));\n\n this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer);\n } else {\n this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer);\n }\n\n this.register(this._terminal.onResize(e => this._handleResize(e.rows)));\n this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));\n this.register(this._terminal.onScroll(() => this._refreshRows()));\n // Line feed is an issue as the prompt won't be read out after a command is run\n this.register(this._terminal.onA11yChar(char => this._handleChar(char)));\n this.register(this._terminal.onLineFeed(() => this._handleChar('\\n')));\n this.register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount)));\n this.register(this._terminal.onKey(e => this._handleKey(e.key)));\n this.register(this._terminal.onBlur(() => this._clearLiveRegion()));\n this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));\n this.register(addDisposableDomListener(document, 'selectionchange', () => this._handleSelectionChange()));\n this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));\n\n this._refreshRows();\n this.register(toDisposable(() => {\n if (DEBUG) {\n this._debugRootContainer!.remove();\n } else {\n this._accessibilityContainer.remove();\n }\n this._rowElements.length = 0;\n }));\n }\n\n private _handleTab(spaceCount: number): void {\n for (let i = 0; i < spaceCount; i++) {\n this._handleChar(' ');\n }\n }\n\n private _handleChar(char: string): void {\n if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {\n if (this._charsToConsume.length > 0) {\n // Have the screen reader ignore the char if it was just input\n const shiftedChar = this._charsToConsume.shift();\n if (shiftedChar !== char) {\n this._charsToAnnounce += char;\n }\n } else {\n this._charsToAnnounce += char;\n }\n\n if (char === '\\n') {\n this._liveRegionLineCount++;\n if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {\n this._liveRegion.textContent += Strings.tooMuchOutput;\n }\n }\n }\n }\n\n private _clearLiveRegion(): void {\n this._liveRegion.textContent = '';\n this._liveRegionLineCount = 0;\n }\n\n private _handleKey(keyChar: string): void {\n this._clearLiveRegion();\n // Only add the char if there is no control character.\n if (!/\\p{Control}/u.test(keyChar)) {\n this._charsToConsume.push(keyChar);\n }\n }\n\n private _refreshRows(start?: number, end?: number): void {\n this._liveRegionDebouncer.refresh(start, end, this._terminal.rows);\n }\n\n private _renderRows(start: number, end: number): void {\n const buffer: IBuffer = this._terminal.buffer;\n const setSize = buffer.lines.length.toString();\n for (let i = start; i <= end; i++) {\n const line = buffer.lines.get(buffer.ydisp + i);\n const columns: number[] = [];\n const lineData = line?.translateToString(true, undefined, undefined, columns) || '';\n const posInSet = (buffer.ydisp + i + 1).toString();\n const element = this._rowElements[i];\n if (element) {\n if (lineData.length === 0) {\n element.innerText = '\\u00a0';\n this._rowColumns.set(element, [0, 1]);\n } else {\n element.textContent = lineData;\n this._rowColumns.set(element, columns);\n }\n element.setAttribute('aria-posinset', posInSet);\n element.setAttribute('aria-setsize', setSize);\n }\n }\n this._announceCharacters();\n }\n\n private _announceCharacters(): void {\n if (this._charsToAnnounce.length === 0) {\n return;\n }\n this._liveRegion.textContent += this._charsToAnnounce;\n this._charsToAnnounce = '';\n }\n\n private _handleBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {\n const boundaryElement = e.target as HTMLElement;\n const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];\n\n // Don't scroll if the buffer top has reached the end in that direction\n const posInSet = boundaryElement.getAttribute('aria-posinset');\n const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;\n if (posInSet === lastRowPos) {\n return;\n }\n\n // Don't scroll when the last focused item was not the second row (focus is going the other\n // direction)\n if (e.relatedTarget !== beforeBoundaryElement) {\n return;\n }\n\n // Remove old boundary element from array\n let topBoundaryElement: HTMLElement;\n let bottomBoundaryElement: HTMLElement;\n if (position === BoundaryPosition.TOP) {\n topBoundaryElement = boundaryElement;\n bottomBoundaryElement = this._rowElements.pop()!;\n this._rowContainer.removeChild(bottomBoundaryElement);\n } else {\n topBoundaryElement = this._rowElements.shift()!;\n bottomBoundaryElement = boundaryElement;\n this._rowContainer.removeChild(topBoundaryElement);\n }\n\n // Remove listeners from old boundary elements\n topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);\n bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Add new element to array/DOM\n if (position === BoundaryPosition.TOP) {\n const newElement = this._createAccessibilityTreeNode();\n this._rowElements.unshift(newElement);\n this._rowContainer.insertAdjacentElement('afterbegin', newElement);\n } else {\n const newElement = this._createAccessibilityTreeNode();\n this._rowElements.push(newElement);\n this._rowContainer.appendChild(newElement);\n }\n\n // Add listeners to new boundary elements\n this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);\n this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Scroll up\n this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);\n\n // Focus new boundary before element\n this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();\n\n // Prevent the standard behavior\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n\n private _handleSelectionChange(): void {\n if (this._rowElements.length === 0) {\n return;\n }\n\n const selection = document.getSelection();\n if (!selection) {\n return;\n }\n\n if (selection.isCollapsed) {\n // Only do something when the anchorNode is inside the row container. This\n // behavior mirrors what we do with mouse --- if the mouse clicks\n // somewhere outside of the terminal, we don't clear the selection.\n if (this._rowContainer.contains(selection.anchorNode)) {\n this._terminal.clearSelection();\n }\n return;\n }\n\n if (!selection.anchorNode || !selection.focusNode) {\n console.error('anchorNode and/or focusNode are null');\n return;\n }\n\n // Sort the two selection points in document order.\n let begin = { node: selection.anchorNode, offset: selection.anchorOffset };\n let end = { node: selection.focusNode, offset: selection.focusOffset };\n if ((begin.node.compareDocumentPosition(end.node) & Node.DOCUMENT_POSITION_PRECEDING) || (begin.node === end.node && begin.offset > end.offset) ) {\n [begin, end] = [end, begin];\n }\n\n // Clamp begin/end to the inside of the row container.\n if (begin.node.compareDocumentPosition(this._rowElements[0]) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING)) {\n begin = { node: this._rowElements[0].childNodes[0], offset: 0 };\n }\n if (!this._rowContainer.contains(begin.node)) {\n // This happens when `begin` is below the last row.\n return;\n }\n const lastRowElement = this._rowElements.slice(-1)[0];\n if (end.node.compareDocumentPosition(lastRowElement) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_PRECEDING)) {\n end = {\n node: lastRowElement,\n offset: lastRowElement.textContent?.length ?? 0\n };\n }\n if (!this._rowContainer.contains(end.node)) {\n // This happens when `end` is above the first row.\n return;\n }\n\n const toRowColumn = ({ node, offset }: typeof begin): {row: number, column: number} | null => {\n // `node` is either the row element or the Text node inside it.\n const rowElement: any = node instanceof Text ? node.parentNode : node;\n let row = parseInt(rowElement?.getAttribute('aria-posinset'), 10) - 1;\n if (isNaN(row)) {\n console.warn('row is invalid. Race condition?');\n return null;\n }\n\n const columns = this._rowColumns.get(rowElement);\n if (!columns) {\n console.warn('columns is null. Race condition?');\n return null;\n }\n\n let column = offset < columns.length ? columns[offset] : columns.slice(-1)[0] + 1;\n if (column >= this._terminal.cols) {\n ++row;\n column = 0;\n }\n return {\n row,\n column\n };\n };\n\n const beginRowColumn = toRowColumn(begin);\n const endRowColumn = toRowColumn(end);\n\n if (!beginRowColumn || !endRowColumn) {\n return;\n }\n\n if (beginRowColumn.row > endRowColumn.row || (beginRowColumn.row === endRowColumn.row && beginRowColumn.column >= endRowColumn.column)) {\n // This should not happen unless we have some bugs.\n throw new Error('invalid range');\n }\n\n this._terminal.select(\n beginRowColumn.column,\n beginRowColumn.row,\n (endRowColumn.row - beginRowColumn.row) * this._terminal.cols - beginRowColumn.column + endRowColumn.column\n );\n }\n\n private _handleResize(rows: number): void {\n // Remove bottom boundary listener\n this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Grow rows as required\n for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {\n this._rowElements[i] = this._createAccessibilityTreeNode();\n this._rowContainer.appendChild(this._rowElements[i]);\n }\n // Shrink rows as required\n while (this._rowElements.length > rows) {\n this._rowContainer.removeChild(this._rowElements.pop()!);\n }\n\n // Add bottom boundary listener\n this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n this._refreshRowsDimensions();\n }\n\n private _createAccessibilityTreeNode(): HTMLElement {\n const element = this._coreBrowserService.mainDocument.createElement('div');\n element.setAttribute('role', 'listitem');\n element.tabIndex = -1;\n this._refreshRowDimensions(element);\n return element;\n }\n private _refreshRowsDimensions(): void {\n if (!this._renderService.dimensions.css.cell.height) {\n return;\n }\n this._accessibilityContainer.style.width = `${this._renderService.dimensions.css.canvas.width}px`;\n if (this._rowElements.length !== this._terminal.rows) {\n this._handleResize(this._terminal.rows);\n }\n for (let i = 0; i < this._terminal.rows; i++) {\n this._refreshRowDimensions(this._rowElements[i]);\n }\n }\n private _refreshRowDimensions(element: HTMLElement): void {\n element.style.height = `${this._renderService.dimensions.css.cell.height}px`;\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISelectionService } from 'browser/services/Services';\nimport { ICoreService, IOptionsService } from 'common/services/Services';\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string): string {\n return text.replace(/\\r?\\n/g, '\\r');\n}\n\n/**\n * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste\n * @param text The pasted text to bracket\n */\nexport function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {\n if (bracketedPasteMode) {\n return '\\x1b[200~' + text + '\\x1b[201~';\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void {\n if (ev.clipboardData) {\n ev.clipboardData.setData('text/plain', selectionService.selectionText);\n }\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n */\nexport function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {\n ev.stopPropagation();\n if (ev.clipboardData) {\n const text = ev.clipboardData.getData('text/plain');\n paste(text, textarea, coreService, optionsService);\n }\n}\n\nexport function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {\n text = prepareTextForTerminal(text);\n text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode && optionsService.rawOptions.ignoreBracketedPasteMode !== true);\n coreService.triggerDataEvent(text, true);\n textarea.value = '';\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void {\n\n // Calculate textarea position relative to the screen element\n const pos = screenElement.getBoundingClientRect();\n const left = ev.clientX - pos.left - 10;\n const top = ev.clientY - pos.top - 10;\n\n // Bring textarea at the cursor position\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = `${left}px`;\n textarea.style.top = `${top}px`;\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {\n moveTextAreaUnderMouseCursor(ev, textarea, screenElement);\n\n if (shouldSelectWord) {\n selectionService.rightClickSelect(ev);\n }\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionService.selectionText;\n textarea.select();\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorContrastCache } from 'browser/Types';\nimport { IColor } from 'common/Types';\nimport { TwoKeyMap } from 'common/MultiKeyMap';\n\nexport class ColorContrastCache implements IColorContrastCache {\n private _color: TwoKeyMap* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();\n private _css: TwoKeyMap* bg */number, /* fg */number, string | null> = new TwoKeyMap();\n\n public setCss(bg: number, fg: number, value: string | null): void {\n this._css.set(bg, fg, value);\n }\n\n public getCss(bg: number, fg: number): string | null | undefined {\n return this._css.get(bg, fg);\n }\n\n public setColor(bg: number, fg: number, value: IColor | null): void {\n this._color.set(bg, fg, value);\n }\n\n public getColor(bg: number, fg: number): IColor | null | undefined {\n return this._color.get(bg, fg);\n }\n\n public clear(): void {\n this._color.clear();\n this._css.clear();\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from 'common/Types';\n\n/**\n * Adds a disposable listener to a node in the DOM, returning the disposable.\n * @param node The node to add a listener to.\n * @param type The event type.\n * @param handler The handler for the listener.\n * @param options The boolean or options object to pass on to the event\n * listener.\n */\nexport function addDisposableDomListener(\n node: Element | Window | Document,\n type: string,\n handler: (e: any) => void,\n options?: boolean | AddEventListenerOptions\n): IDisposable {\n node.addEventListener(type, handler, options);\n let disposed = false;\n return {\n dispose: () => {\n if (disposed) {\n return;\n }\n disposed = true;\n node.removeEventListener(type, handler, options);\n }\n };\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { addDisposableDomListener } from 'browser/Lifecycle';\nimport { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from 'browser/Types';\nimport { EventEmitter } from 'common/EventEmitter';\nimport { Disposable, disposeArray, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle';\nimport { IDisposable } from 'common/Types';\nimport { IBufferService } from 'common/services/Services';\nimport { ILinkProviderService, IMouseService, IRenderService } from './services/Services';\n\nexport class Linkifier extends Disposable implements ILinkifier2 {\n public get currentLink(): ILinkWithState | undefined { return this._currentLink; }\n protected _currentLink: ILinkWithState | undefined;\n private _mouseDownLink: ILinkWithState | undefined;\n private _lastMouseEvent: MouseEvent | undefined;\n private _linkCacheDisposables: IDisposable[] = [];\n private _lastBufferCell: IBufferCellPosition | undefined;\n private _isMouseOut: boolean = true;\n private _wasResized: boolean = false;\n private _activeProviderReplies: Map
| undefined;\n private _activeLine: number = -1;\n\n private readonly _onShowLinkUnderline = this.register(new EventEmitter());\n public readonly onShowLinkUnderline = this._onShowLinkUnderline.event;\n private readonly _onHideLinkUnderline = this.register(new EventEmitter());\n public readonly onHideLinkUnderline = this._onHideLinkUnderline.event;\n\n constructor(\n private readonly _element: HTMLElement,\n @IMouseService private readonly _mouseService: IMouseService,\n @IRenderService private readonly _renderService: IRenderService,\n @IBufferService private readonly _bufferService: IBufferService,\n @ILinkProviderService private readonly _linkProviderService: ILinkProviderService\n ) {\n super();\n this.register(getDisposeArrayDisposable(this._linkCacheDisposables));\n this.register(toDisposable(() => {\n this._lastMouseEvent = undefined;\n // Clear out link providers as they could easily cause an embedder memory leak\n this._activeProviderReplies?.clear();\n }));\n // Listen to resize to catch the case where it's resized and the cursor is out of the viewport.\n this.register(this._bufferService.onResize(() => {\n this._clearCurrentLink();\n this._wasResized = true;\n }));\n this.register(addDisposableDomListener(this._element, 'mouseleave', () => {\n this._isMouseOut = true;\n this._clearCurrentLink();\n }));\n this.register(addDisposableDomListener(this._element, 'mousemove', this._handleMouseMove.bind(this)));\n this.register(addDisposableDomListener(this._element, 'mousedown', this._handleMouseDown.bind(this)));\n this.register(addDisposableDomListener(this._element, 'mouseup', this._handleMouseUp.bind(this)));\n }\n\n private _handleMouseMove(event: MouseEvent): void {\n this._lastMouseEvent = event;\n\n const position = this._positionFromMouseEvent(event, this._element, this._mouseService);\n if (!position) {\n return;\n }\n this._isMouseOut = false;\n\n // Ignore the event if it's an embedder created hover widget\n const composedPath = event.composedPath() as HTMLElement[];\n for (let i = 0; i < composedPath.length; i++) {\n const target = composedPath[i];\n // Hit Terminal.element, break and continue\n if (target.classList.contains('xterm')) {\n break;\n }\n // It's a hover, don't respect hover event\n if (target.classList.contains('xterm-hover')) {\n return;\n }\n }\n\n if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {\n this._handleHover(position);\n this._lastBufferCell = position;\n }\n }\n\n private _handleHover(position: IBufferCellPosition): void {\n // TODO: This currently does not cache link provider results across wrapped lines, activeLine\n // should be something like `activeRange: {startY, endY}`\n // Check if we need to clear the link\n if (this._activeLine !== position.y || this._wasResized) {\n this._clearCurrentLink();\n this._askForLink(position, false);\n this._wasResized = false;\n return;\n }\n\n // Check the if the link is in the mouse position\n const isCurrentLinkInPosition = this._currentLink && this._linkAtPosition(this._currentLink.link, position);\n if (!isCurrentLinkInPosition) {\n this._clearCurrentLink();\n this._askForLink(position, true);\n }\n }\n\n private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {\n if (!this._activeProviderReplies || !useLineCache) {\n this._activeProviderReplies?.forEach(reply => {\n reply?.forEach(linkWithState => {\n if (linkWithState.link.dispose) {\n linkWithState.link.dispose();\n }\n });\n });\n this._activeProviderReplies = new Map();\n this._activeLine = position.y;\n }\n let linkProvided = false;\n\n // There is no link cached, so ask for one\n for (const [i, linkProvider] of this._linkProviderService.linkProviders.entries()) {\n if (useLineCache) {\n const existingReply = this._activeProviderReplies?.get(i);\n // If there isn't a reply, the provider hasn't responded yet.\n\n // TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring\n // provideLinks isn't triggered again saves ILink.hover firing twice though. This probably\n // needs promises to get fixed\n if (existingReply) {\n linkProvided = this._checkLinkProviderResult(i, position, linkProvided);\n }\n } else {\n linkProvider.provideLinks(position.y, (links: ILink[] | undefined) => {\n if (this._isMouseOut) {\n return;\n }\n const linksWithState: ILinkWithState[] | undefined = links?.map(link => ({ link }));\n this._activeProviderReplies?.set(i, linksWithState);\n linkProvided = this._checkLinkProviderResult(i, position, linkProvided);\n\n // If all providers have responded, remove lower priority links that intersect ranges of\n // higher priority links\n if (this._activeProviderReplies?.size === this._linkProviderService.linkProviders.length) {\n this._removeIntersectingLinks(position.y, this._activeProviderReplies);\n }\n });\n }\n }\n }\n\n private _removeIntersectingLinks(y: number, replies: Map): void {\n const occupiedCells = new Set();\n for (let i = 0; i < replies.size; i++) {\n const providerReply = replies.get(i);\n if (!providerReply) {\n continue;\n }\n for (let i = 0; i < providerReply.length; i++) {\n const linkWithState = providerReply[i];\n const startX = linkWithState.link.range.start.y < y ? 0 : linkWithState.link.range.start.x;\n const endX = linkWithState.link.range.end.y > y ? this._bufferService.cols : linkWithState.link.range.end.x;\n for (let x = startX; x <= endX; x++) {\n if (occupiedCells.has(x)) {\n providerReply.splice(i--, 1);\n break;\n }\n occupiedCells.add(x);\n }\n }\n }\n }\n\n private _checkLinkProviderResult(index: number, position: IBufferCellPosition, linkProvided: boolean): boolean {\n if (!this._activeProviderReplies) {\n return linkProvided;\n }\n\n const links = this._activeProviderReplies.get(index);\n\n // Check if every provider before this one has come back undefined\n let hasLinkBefore = false;\n for (let j = 0; j < index; j++) {\n if (!this._activeProviderReplies.has(j) || this._activeProviderReplies.get(j)) {\n hasLinkBefore = true;\n }\n }\n\n // If all providers with higher priority came back undefined, then this provider's link for\n // the position should be used\n if (!hasLinkBefore && links) {\n const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position));\n if (linkAtPosition) {\n linkProvided = true;\n this._handleNewLink(linkAtPosition);\n }\n }\n\n // Check if all the providers have responded\n if (this._activeProviderReplies.size === this._linkProviderService.linkProviders.length && !linkProvided) {\n // Respect the order of the link providers\n for (let j = 0; j < this._activeProviderReplies.size; j++) {\n const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position));\n if (currentLink) {\n linkProvided = true;\n this._handleNewLink(currentLink);\n break;\n }\n }\n }\n\n return linkProvided;\n }\n\n private _handleMouseDown(): void {\n this._mouseDownLink = this._currentLink;\n }\n\n private _handleMouseUp(event: MouseEvent): void {\n if (!this._currentLink) {\n return;\n }\n\n const position = this._positionFromMouseEvent(event, this._element, this._mouseService);\n if (!position) {\n return;\n }\n\n if (this._mouseDownLink === this._currentLink && this._linkAtPosition(this._currentLink.link, position)) {\n this._currentLink.link.activate(event, this._currentLink.link.text);\n }\n }\n\n private _clearCurrentLink(startRow?: number, endRow?: number): void {\n if (!this._currentLink || !this._lastMouseEvent) {\n return;\n }\n\n // If we have a start and end row, check that the link is within it\n if (!startRow || !endRow || (this._currentLink.link.range.start.y >= startRow && this._currentLink.link.range.end.y <= endRow)) {\n this._linkLeave(this._element, this._currentLink.link, this._lastMouseEvent);\n this._currentLink = undefined;\n disposeArray(this._linkCacheDisposables);\n }\n }\n\n private _handleNewLink(linkWithState: ILinkWithState): void {\n if (!this._lastMouseEvent) {\n return;\n }\n\n const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService);\n\n if (!position) {\n return;\n }\n\n // Trigger hover if the we have a link at the position\n if (this._linkAtPosition(linkWithState.link, position)) {\n this._currentLink = linkWithState;\n this._currentLink.state = {\n decorations: {\n underline: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.underline,\n pointerCursor: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.pointerCursor\n },\n isHovered: true\n };\n this._linkHover(this._element, linkWithState.link, this._lastMouseEvent);\n\n // Add listener for tracking decorations changes\n linkWithState.link.decorations = {} as ILinkDecorations;\n Object.defineProperties(linkWithState.link.decorations, {\n pointerCursor: {\n get: () => this._currentLink?.state?.decorations.pointerCursor,\n set: v => {\n if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) {\n this._currentLink.state.decorations.pointerCursor = v;\n if (this._currentLink.state.isHovered) {\n this._element.classList.toggle('xterm-cursor-pointer', v);\n }\n }\n }\n },\n underline: {\n get: () => this._currentLink?.state?.decorations.underline,\n set: v => {\n if (this._currentLink?.state && this._currentLink?.state?.decorations.underline !== v) {\n this._currentLink.state.decorations.underline = v;\n if (this._currentLink.state.isHovered) {\n this._fireUnderlineEvent(linkWithState.link, v);\n }\n }\n }\n }\n });\n\n // Listen to viewport changes to re-render the link under the cursor (only when the line the\n // link is on changes)\n this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {\n // Sanity check, this shouldn't happen in practice as this listener would be disposed\n if (!this._currentLink) {\n return;\n }\n // When start is 0 a scroll most likely occurred, make sure links above the fold also get\n // cleared.\n const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;\n const end = this._bufferService.buffer.ydisp + 1 + e.end;\n // Only clear the link if the viewport change happened on this line\n if (this._currentLink.link.range.start.y >= start && this._currentLink.link.range.end.y <= end) {\n this._clearCurrentLink(start, end);\n if (this._lastMouseEvent) {\n // re-eval previously active link after changes\n const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService!);\n if (position) {\n this._askForLink(position, false);\n }\n }\n }\n }));\n }\n }\n\n protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {\n if (this._currentLink?.state) {\n this._currentLink.state.isHovered = true;\n if (this._currentLink.state.decorations.underline) {\n this._fireUnderlineEvent(link, true);\n }\n if (this._currentLink.state.decorations.pointerCursor) {\n element.classList.add('xterm-cursor-pointer');\n }\n }\n\n if (link.hover) {\n link.hover(event, link.text);\n }\n }\n\n private _fireUnderlineEvent(link: ILink, showEvent: boolean): void {\n const range = link.range;\n const scrollOffset = this._bufferService.buffer.ydisp;\n const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);\n const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;\n emitter.fire(event);\n }\n\n protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {\n if (this._currentLink?.state) {\n this._currentLink.state.isHovered = false;\n if (this._currentLink.state.decorations.underline) {\n this._fireUnderlineEvent(link, false);\n }\n if (this._currentLink.state.decorations.pointerCursor) {\n element.classList.remove('xterm-cursor-pointer');\n }\n }\n\n if (link.leave) {\n link.leave(event, link.text);\n }\n }\n\n /**\n * Check if the buffer position is within the link\n * @param link\n * @param position\n */\n private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {\n const lower = link.range.start.y * this._bufferService.cols + link.range.start.x;\n const upper = link.range.end.y * this._bufferService.cols + link.range.end.x;\n const current = position.y * this._bufferService.cols + position.x;\n return (lower <= current && current <= upper);\n }\n\n /**\n * Get the buffer position from a mouse event\n * @param event\n */\n private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement, mouseService: IMouseService): IBufferCellPosition | undefined {\n const coords = mouseService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);\n if (!coords) {\n return;\n }\n\n return { x: coords[0], y: coords[1] + this._bufferService.buffer.ydisp };\n }\n\n private _createLinkUnderlineEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {\n return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// This file contains strings that get exported in the API so they can be localized\n\n// eslint-disable-next-line prefer-const\nexport let promptLabel = 'Terminal input';\n\n// eslint-disable-next-line prefer-const\nexport let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';\n","/**\n * Copyright (c) 2022 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IBufferRange, ILink } from 'browser/Types';\nimport { ILinkProvider } from 'browser/services/Services';\nimport { CellData } from 'common/buffer/CellData';\nimport { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';\n\nexport class OscLinkProvider implements ILinkProvider {\n constructor(\n @IBufferService private readonly _bufferService: IBufferService,\n @IOptionsService private readonly _optionsService: IOptionsService,\n @IOscLinkService private readonly _oscLinkService: IOscLinkService\n ) {\n }\n\n public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {\n const line = this._bufferService.buffer.lines.get(y - 1);\n if (!line) {\n callback(undefined);\n return;\n }\n\n const result: ILink[] = [];\n const linkHandler = this._optionsService.rawOptions.linkHandler;\n const cell = new CellData();\n const lineLength = line.getTrimmedLength();\n let currentLinkId = -1;\n let currentStart = -1;\n let finishLink = false;\n for (let x = 0; x < lineLength; x++) {\n // Minor optimization, only check for content if there isn't a link in case the link ends with\n // a null cell\n if (currentStart === -1 && !line.hasContent(x)) {\n continue;\n }\n\n line.loadCell(x, cell);\n if (cell.hasExtendedAttrs() && cell.extended.urlId) {\n if (currentStart === -1) {\n currentStart = x;\n currentLinkId = cell.extended.urlId;\n continue;\n } else {\n finishLink = cell.extended.urlId !== currentLinkId;\n }\n } else {\n if (currentStart !== -1) {\n finishLink = true;\n }\n }\n\n if (finishLink || (currentStart !== -1 && x === lineLength - 1)) {\n const text = this._oscLinkService.getLinkData(currentLinkId)?.uri;\n if (text) {\n // These ranges are 1-based\n const range: IBufferRange = {\n start: {\n x: currentStart + 1,\n y\n },\n end: {\n // Offset end x if it's a link that ends on the last cell in the line\n x: x + (!finishLink && x === lineLength - 1 ? 1 : 0),\n y\n }\n };\n\n let ignoreLink = false;\n if (!linkHandler?.allowNonHttpProtocols) {\n try {\n const parsed = new URL(text);\n if (!['http:', 'https:'].includes(parsed.protocol)) {\n ignoreLink = true;\n }\n } catch (e) {\n // Ignore invalid URLs to prevent unexpected behaviors\n ignoreLink = true;\n }\n }\n\n if (!ignoreLink) {\n // OSC links always use underline and pointer decorations\n result.push({\n text,\n range,\n activate: (e, text) => (linkHandler ? linkHandler.activate(e, text, range) : defaultActivate(e, text)),\n hover: (e, text) => linkHandler?.hover?.(e, text, range),\n leave: (e, text) => linkHandler?.leave?.(e, text, range)\n });\n }\n }\n finishLink = false;\n\n // Clear link or start a new link if one starts immediately\n if (cell.hasExtendedAttrs() && cell.extended.urlId) {\n currentStart = x;\n currentLinkId = cell.extended.urlId;\n } else {\n currentStart = -1;\n currentLinkId = -1;\n }\n }\n }\n\n // TODO: Handle fetching and returning other link ranges to underline other links with the same\n // id\n callback(result);\n }\n}\n\nfunction defaultActivate(e: MouseEvent, uri: string): void {\n const answer = confirm(`Do you want to navigate to ${uri}?\\n\\nWARNING: This link could potentially be dangerous`);\n if (answer) {\n const newWindow = window.open();\n if (newWindow) {\n try {\n newWindow.opener = null;\n } catch {\n // no-op, Electron can throw\n }\n newWindow.location.href = uri;\n } else {\n console.warn('Opening link blocked as opener could not be cleared');\n }\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDebouncerWithCallback } from 'browser/Types';\nimport { ICoreBrowserService } from 'browser/services/Services';\n\n/**\n * Debounces calls to render terminal rows using animation frames.\n */\nexport class RenderDebouncer implements IRenderDebouncerWithCallback {\n private _rowStart: number | undefined;\n private _rowEnd: number | undefined;\n private _rowCount: number | undefined;\n private _animationFrame: number | undefined;\n private _refreshCallbacks: FrameRequestCallback[] = [];\n\n constructor(\n private _renderCallback: (start: number, end: number) => void,\n private readonly _coreBrowserService: ICoreBrowserService\n ) {\n }\n\n public dispose(): void {\n if (this._animationFrame) {\n this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = undefined;\n }\n }\n\n public addRefreshCallback(callback: FrameRequestCallback): number {\n this._refreshCallbacks.push(callback);\n if (!this._animationFrame) {\n this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());\n }\n return this._animationFrame;\n }\n\n public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {\n this._rowCount = rowCount;\n // Get the min/max row start/end for the arg values\n rowStart = rowStart !== undefined ? rowStart : 0;\n rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;\n // Set the properties to the updated values\n this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;\n this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;\n\n if (this._animationFrame) {\n return;\n }\n\n this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());\n }\n\n private _innerRefresh(): void {\n this._animationFrame = undefined;\n\n // Make sure values are set\n if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {\n this._runRefreshCallbacks();\n return;\n }\n\n // Clamp values\n const start = Math.max(this._rowStart, 0);\n const end = Math.min(this._rowEnd, this._rowCount - 1);\n\n // Reset debouncer (this happens before render callback as the render could trigger it again)\n this._rowStart = undefined;\n this._rowEnd = undefined;\n\n // Run render callback\n this._renderCallback(start, end);\n this._runRefreshCallbacks();\n }\n\n private _runRefreshCallbacks(): void {\n for (const callback of this._refreshCallbacks) {\n callback(0);\n }\n this._refreshCallbacks = [];\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n *\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\nimport { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';\nimport { addDisposableDomListener } from 'browser/Lifecycle';\nimport { Linkifier } from './Linkifier';\nimport * as Strings from 'browser/LocalizableStrings';\nimport { OscLinkProvider } from 'browser/OscLinkProvider';\nimport { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';\nimport { Viewport } from 'browser/Viewport';\nimport { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';\nimport { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';\nimport { CompositionHelper } from 'browser/input/CompositionHelper';\nimport { DomRenderer } from 'browser/renderer/dom/DomRenderer';\nimport { IRenderer } from 'browser/renderer/shared/Types';\nimport { CharSizeService } from 'browser/services/CharSizeService';\nimport { CharacterJoinerService } from 'browser/services/CharacterJoinerService';\nimport { CoreBrowserService } from 'browser/services/CoreBrowserService';\nimport { MouseService } from 'browser/services/MouseService';\nimport { RenderService } from 'browser/services/RenderService';\nimport { SelectionService } from 'browser/services/SelectionService';\nimport { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';\nimport { ThemeService } from 'browser/services/ThemeService';\nimport { channels, color } from 'common/Color';\nimport { CoreTerminal } from 'common/CoreTerminal';\nimport { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';\nimport { MutableDisposable, toDisposable } from 'common/Lifecycle';\nimport * as Browser from 'common/Platform';\nimport { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types';\nimport { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';\nimport { IBuffer } from 'common/buffer/Types';\nimport { C0, C1_ESCAPED } from 'common/data/EscapeSequences';\nimport { evaluateKeyboardEvent } from 'common/input/Keyboard';\nimport { toRgbString } from 'common/input/XParseColor';\nimport { DecorationService } from 'common/services/DecorationService';\nimport { IDecorationService } from 'common/services/Services';\nimport { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm';\nimport { WindowsOptionsReportType } from '../common/InputHandler';\nimport { AccessibilityManager } from './AccessibilityManager';\nimport { LinkProviderService } from 'browser/services/LinkProviderService';\n\nexport class Terminal extends CoreTerminal implements ITerminal {\n public textarea: HTMLTextAreaElement | undefined;\n public element: HTMLElement | undefined;\n public screenElement: HTMLElement | undefined;\n\n private _document: Document | undefined;\n private _viewportScrollArea: HTMLElement | undefined;\n private _viewportElement: HTMLElement | undefined;\n private _helperContainer: HTMLElement | undefined;\n private _compositionView: HTMLElement | undefined;\n\n public linkifier: ILinkifier2 | undefined;\n private _overviewRulerRenderer: OverviewRulerRenderer | undefined;\n\n public browser: IBrowser = Browser as any;\n\n private _customKeyEventHandler: CustomKeyEventHandler | undefined;\n private _customWheelEventHandler: CustomWheelEventHandler | undefined;\n\n // Browser services\n private _decorationService: DecorationService;\n private _linkProviderService: ILinkProviderService;\n\n // Optional browser services\n private _charSizeService: ICharSizeService | undefined;\n private _coreBrowserService: ICoreBrowserService | undefined;\n private _mouseService: IMouseService | undefined;\n private _renderService: IRenderService | undefined;\n private _themeService: IThemeService | undefined;\n private _characterJoinerService: ICharacterJoinerService | undefined;\n private _selectionService: ISelectionService | undefined;\n\n /**\n * Records whether the keydown event has already been handled and triggered a data event, if so\n * the keypress event should not trigger a data event but should still print to the textarea so\n * screen readers will announce it.\n */\n private _keyDownHandled: boolean = false;\n\n /**\n * Records whether a keydown event has occured since the last keyup event, i.e. whether a key\n * is currently \"pressed\".\n */\n private _keyDownSeen: boolean = false;\n\n /**\n * Records whether the keypress event has already been handled and triggered a data event, if so\n * the input event should not trigger a data event but should still print to the textarea so\n * screen readers will announce it.\n */\n private _keyPressHandled: boolean = false;\n\n /**\n * Records whether there has been a keydown event for a dead key without a corresponding keydown\n * event for the composed/alternative character. If we cancel the keydown event for the dead key,\n * no events will be emitted for the final character.\n */\n private _unprocessedDeadKey: boolean = false;\n\n public viewport: IViewport | undefined;\n private _compositionHelper: ICompositionHelper | undefined;\n private _accessibilityManager: MutableDisposable = this.register(new MutableDisposable());\n\n private readonly _onCursorMove = this.register(new EventEmitter());\n public readonly onCursorMove = this._onCursorMove.event;\n private readonly _onKey = this.register(new EventEmitter<{ key: string, domEvent: KeyboardEvent }>());\n public readonly onKey = this._onKey.event;\n private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>());\n public readonly onRender = this._onRender.event;\n private readonly _onSelectionChange = this.register(new EventEmitter());\n public readonly onSelectionChange = this._onSelectionChange.event;\n private readonly _onTitleChange = this.register(new EventEmitter());\n public readonly onTitleChange = this._onTitleChange.event;\n private readonly _onBell = this.register(new EventEmitter());\n public readonly onBell = this._onBell.event;\n\n private _onFocus = this.register(new EventEmitter());\n public get onFocus(): IEvent { return this._onFocus.event; }\n private _onBlur = this.register(new EventEmitter());\n public get onBlur(): IEvent { return this._onBlur.event; }\n private _onA11yCharEmitter = this.register(new EventEmitter());\n public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; }\n private _onA11yTabEmitter = this.register(new EventEmitter());\n public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; }\n private _onWillOpen = this.register(new EventEmitter());\n public get onWillOpen(): IEvent { return this._onWillOpen.event; }\n\n constructor(\n options: Partial = {}\n ) {\n super(options);\n\n this._setup();\n\n this._decorationService = this._instantiationService.createInstance(DecorationService);\n this._instantiationService.setService(IDecorationService, this._decorationService);\n this._linkProviderService = this._instantiationService.createInstance(LinkProviderService);\n this._instantiationService.setService(ILinkProviderService, this._linkProviderService);\n this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));\n\n // Setup InputHandler listeners\n this.register(this._inputHandler.onRequestBell(() => this._onBell.fire()));\n this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));\n this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));\n this.register(this._inputHandler.onRequestReset(() => this.reset()));\n this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));\n this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));\n this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));\n this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));\n this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));\n this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));\n\n // Setup listeners\n this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));\n\n this.register(toDisposable(() => {\n this._customKeyEventHandler = undefined;\n this.element?.parentNode?.removeChild(this.element);\n }));\n }\n\n /**\n * Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112.\n * An event from OSC 4|104 may contain multiple set or report requests, and multiple\n * or none restore requests (resetting all),\n * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.\n */\n private _handleColorEvent(event: IColorEvent): void {\n if (!this._themeService) return;\n for (const req of event) {\n let acc: 'foreground' | 'background' | 'cursor' | 'ansi';\n let ident = '';\n switch (req.index) {\n case SpecialColorIndex.FOREGROUND: // OSC 10 | 110\n acc = 'foreground';\n ident = '10';\n break;\n case SpecialColorIndex.BACKGROUND: // OSC 11 | 111\n acc = 'background';\n ident = '11';\n break;\n case SpecialColorIndex.CURSOR: // OSC 12 | 112\n acc = 'cursor';\n ident = '12';\n break;\n default: // OSC 4 | 104\n // we can skip the [0..255] range check here (already done in inputhandler)\n acc = 'ansi';\n ident = '4;' + req.index;\n }\n switch (req.type) {\n case ColorRequestType.REPORT:\n const colorRgb = color.toColorRGB(acc === 'ansi'\n ? this._themeService.colors.ansi[req.index]\n : this._themeService.colors[acc]);\n this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(colorRgb)}${C1_ESCAPED.ST}`);\n break;\n case ColorRequestType.SET:\n if (acc === 'ansi') {\n this._themeService.modifyColors(colors => colors.ansi[req.index] = channels.toColor(...req.color));\n } else {\n const narrowedAcc = acc;\n this._themeService.modifyColors(colors => colors[narrowedAcc] = channels.toColor(...req.color));\n }\n break;\n case ColorRequestType.RESTORE:\n this._themeService.restoreColor(req.index);\n break;\n }\n }\n }\n\n protected _setup(): void {\n super._setup();\n\n this._customKeyEventHandler = undefined;\n }\n\n /**\n * Convenience property to active buffer.\n */\n public get buffer(): IBuffer {\n return this.buffers.active;\n }\n\n /**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\n public focus(): void {\n if (this.textarea) {\n this.textarea.focus({ preventScroll: true });\n }\n }\n\n private _handleScreenReaderModeOptionChange(value: boolean): void {\n if (value) {\n if (!this._accessibilityManager.value && this._renderService) {\n this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);\n }\n } else {\n this._accessibilityManager.clear();\n }\n }\n\n /**\n * Binds the desired focus behavior on a given terminal object.\n */\n private _handleTextAreaFocus(ev: FocusEvent): void {\n if (this.coreService.decPrivateModes.sendFocus) {\n this.coreService.triggerDataEvent(C0.ESC + '[I');\n }\n this.element!.classList.add('focus');\n this._showCursor();\n this._onFocus.fire();\n }\n\n /**\n * Blur the terminal, calling the blur function on the terminal's underlying\n * textarea.\n */\n public blur(): void {\n return this.textarea?.blur();\n }\n\n /**\n * Binds the desired blur behavior on a given terminal object.\n */\n private _handleTextAreaBlur(): void {\n // Text can safely be removed on blur. Doing it earlier could interfere with\n // screen readers reading it out.\n this.textarea!.value = '';\n this.refresh(this.buffer.y, this.buffer.y);\n if (this.coreService.decPrivateModes.sendFocus) {\n this.coreService.triggerDataEvent(C0.ESC + '[O');\n }\n this.element!.classList.remove('focus');\n this._onBlur.fire();\n }\n\n private _syncTextArea(): void {\n if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing || !this._renderService) {\n return;\n }\n const cursorY = this.buffer.ybase + this.buffer.y;\n const bufferLine = this.buffer.lines.get(cursorY);\n if (!bufferLine) {\n return;\n }\n const cursorX = Math.min(this.buffer.x, this.cols - 1);\n const cellHeight = this._renderService.dimensions.css.cell.height;\n const width = bufferLine.getWidth(cursorX);\n const cellWidth = this._renderService.dimensions.css.cell.width * width;\n const cursorTop = this.buffer.y * this._renderService.dimensions.css.cell.height;\n const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width;\n\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n this.textarea.style.left = cursorLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = cellWidth + 'px';\n this.textarea.style.height = cellHeight + 'px';\n this.textarea.style.lineHeight = cellHeight + 'px';\n this.textarea.style.zIndex = '-5';\n }\n\n /**\n * Initialize default behavior\n */\n private _initGlobal(): void {\n this._bindKeys();\n\n // Bind clipboard functionality\n this.register(addDisposableDomListener(this.element!, 'copy', (event: ClipboardEvent) => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!this.hasSelection()) {\n return;\n }\n copyHandler(event, this._selectionService!);\n }));\n const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this.coreService, this.optionsService);\n this.register(addDisposableDomListener(this.textarea!, 'paste', pasteHandlerWrapper));\n this.register(addDisposableDomListener(this.element!, 'paste', pasteHandlerWrapper));\n\n // Handle right click context menus\n if (Browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n this.register(addDisposableDomListener(this.element!, 'mousedown', (event: MouseEvent) => {\n if (event.button === 2) {\n rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);\n }\n }));\n } else {\n this.register(addDisposableDomListener(this.element!, 'contextmenu', (event: MouseEvent) => {\n rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);\n }));\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (Browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n this.register(addDisposableDomListener(this.element!, 'auxclick', (event: MouseEvent) => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea!, this.screenElement!);\n }\n }));\n }\n }\n\n /**\n * Apply key handling to the terminal\n */\n private _bindKeys(): void {\n this.register(addDisposableDomListener(this.textarea!, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true));\n this.register(addDisposableDomListener(this.textarea!, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));\n this.register(addDisposableDomListener(this.textarea!, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));\n this.register(addDisposableDomListener(this.textarea!, 'compositionstart', () => this._compositionHelper!.compositionstart()));\n this.register(addDisposableDomListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e)));\n this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend()));\n this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true));\n this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements()));\n }\n\n /**\n * Opens the terminal within an element.\n *\n * @param parent The element to create the terminal within.\n */\n public open(parent: HTMLElement): void {\n if (!parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n if (!parent.isConnected) {\n this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');\n }\n\n // If the terminal is already opened\n if (this.element?.ownerDocument.defaultView && this._coreBrowserService) {\n // Adjust the window if needed\n if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) {\n this._coreBrowserService.window = this.element.ownerDocument.defaultView;\n }\n return;\n }\n\n this._document = parent.ownerDocument;\n if (this.options.documentOverride && this.options.documentOverride instanceof Document) {\n this._document = this.optionsService.rawOptions.documentOverride as Document;\n }\n\n // Create main element container\n this.element = this._document.createElement('div');\n this.element.dir = 'ltr'; // xterm.css assumes LTR\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n parent.appendChild(this.element);\n\n // Performance: Use a document fragment to build the terminal\n // viewport and helper elements detached from the DOM\n const fragment = this._document.createDocumentFragment();\n this._viewportElement = this._document.createElement('div');\n this._viewportElement.classList.add('xterm-viewport');\n fragment.appendChild(this._viewportElement);\n\n this._viewportScrollArea = this._document.createElement('div');\n this._viewportScrollArea.classList.add('xterm-scroll-area');\n this._viewportElement.appendChild(this._viewportScrollArea);\n\n this.screenElement = this._document.createElement('div');\n this.screenElement.classList.add('xterm-screen');\n this.register(addDisposableDomListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this._helperContainer = this._document.createElement('div');\n this._helperContainer.classList.add('xterm-helpers');\n this.screenElement.appendChild(this._helperContainer);\n fragment.appendChild(this.screenElement);\n\n this.textarea = this._document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('aria-label', Strings.promptLabel);\n if (!Browser.isChromeOS) {\n // ChromeVox on ChromeOS does not like this. See\n // https://issuetracker.google.com/issues/260170397\n this.textarea.setAttribute('aria-multiline', 'false');\n }\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n\n // Register the core browser service before the generic textarea handlers are registered so it\n // handles them first. Otherwise the renderers may use the wrong focus state.\n this._coreBrowserService = this.register(this._instantiationService.createInstance(CoreBrowserService,\n this.textarea,\n parent.ownerDocument.defaultView ?? window,\n // Force unsafe null in node.js environment for tests\n this._document ?? (typeof window !== 'undefined') ? window.document : null as any\n ));\n this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);\n\n this.register(addDisposableDomListener(this.textarea, 'focus', (ev: FocusEvent) => this._handleTextAreaFocus(ev)));\n this.register(addDisposableDomListener(this.textarea, 'blur', () => this._handleTextAreaBlur()));\n this._helperContainer.appendChild(this.textarea);\n\n this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);\n this._instantiationService.setService(ICharSizeService, this._charSizeService);\n\n this._themeService = this._instantiationService.createInstance(ThemeService);\n this._instantiationService.setService(IThemeService, this._themeService);\n\n this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);\n this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);\n\n this._renderService = this.register(this._instantiationService.createInstance(RenderService, this.rows, this.screenElement));\n this._instantiationService.setService(IRenderService, this._renderService);\n this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));\n this.onResize(e => this._renderService!.resize(e.cols, e.rows));\n\n this._compositionView = this._document.createElement('div');\n this._compositionView.classList.add('composition-view');\n this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);\n this._helperContainer.appendChild(this._compositionView);\n\n this._mouseService = this._instantiationService.createInstance(MouseService);\n this._instantiationService.setService(IMouseService, this._mouseService);\n\n this.linkifier = this.register(this._instantiationService.createInstance(Linkifier, this.screenElement));\n\n // Performance: Add viewport and helper elements from the fragment\n this.element.appendChild(fragment);\n\n try {\n this._onWillOpen.fire(this.element);\n }\n catch { /* fails to load addon for some reason */ }\n if (!this._renderService.hasRenderer()) {\n this._renderService.setRenderer(this._createRenderer());\n }\n\n this.viewport = this._instantiationService.createInstance(Viewport, this._viewportElement, this._viewportScrollArea);\n this.viewport.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent, ScrollSource.VIEWPORT)),\n this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));\n this.register(this.viewport);\n\n this.register(this.onCursorMove(() => {\n this._renderService!.handleCursorMove();\n this._syncTextArea();\n }));\n this.register(this.onResize(() => this._renderService!.handleResize(this.cols, this.rows)));\n this.register(this.onBlur(() => this._renderService!.handleBlur()));\n this.register(this.onFocus(() => this._renderService!.handleFocus()));\n this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea()));\n\n this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,\n this.element,\n this.screenElement,\n this.linkifier\n ));\n this._instantiationService.setService(ISelectionService, this._selectionService);\n this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));\n this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));\n this.register(this._selectionService.onRequestRedraw(e => this._renderService!.handleSelectionChanged(e.start, e.end, e.columnSelectMode)));\n this.register(this._selectionService.onLinuxMouseSelection(text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea!.value = text;\n this.textarea!.focus();\n this.textarea!.select();\n }));\n this.register(this._onScroll.event(ev => {\n this.viewport!.syncScrollArea();\n this._selectionService!.refresh();\n }));\n this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh()));\n\n this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));\n this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));\n\n // apply mouse event classes set by escape codes before terminal was attached\n if (this.coreMouseService.areMouseEventsActive) {\n this._selectionService.disable();\n this.element.classList.add('enable-mouse-events');\n } else {\n this._selectionService.enable();\n }\n\n if (this.options.screenReaderMode) {\n // Note that this must be done *after* the renderer is created in order to\n // ensure the correct order of the dprchange event\n this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);\n }\n this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));\n\n if (this.options.overviewRulerWidth) {\n this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));\n }\n this.optionsService.onSpecificOptionChange('overviewRulerWidth', value => {\n if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) {\n this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));\n }\n });\n // Measure the character size\n this._charSizeService.measure();\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that need to be taken on the document.\n this._initGlobal();\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n }\n\n private _createRenderer(): IRenderer {\n return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier!);\n }\n\n /**\n * Bind certain mouse events to the terminal.\n * By default only 3 button + wheel up/down is ativated. For higher buttons\n * no mouse report will be created. Typically the standard actions will be active.\n *\n * There are several reasons not to enable support for higher buttons/wheel:\n * - Button 4 and 5 are typically used for history back and forward navigation,\n * there is no straight forward way to supress/intercept those standard actions.\n * - Support for higher buttons does not work in some platform/browser combinations.\n * - Left/right wheel was not tested.\n * - Emulators vary in mouse button support, typically only 3 buttons and\n * wheel up/down work reliable.\n *\n * TODO: Move mouse event code into its own file.\n */\n public bindMouse(): void {\n const self = this;\n const el = this.element!;\n\n // send event to CoreMouseService\n function sendEvent(ev: MouseEvent | WheelEvent): boolean {\n // get mouse coordinates\n const pos = self._mouseService!.getMouseReportCoords(ev, self.screenElement!);\n if (!pos) {\n return false;\n }\n\n let but: CoreMouseButton;\n let action: CoreMouseAction | undefined;\n switch ((ev as any).overrideType || ev.type) {\n case 'mousemove':\n action = CoreMouseAction.MOVE;\n if (ev.buttons === undefined) {\n // buttons is not supported on macOS, try to get a value from button instead\n but = CoreMouseButton.NONE;\n if (ev.button !== undefined) {\n but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n }\n } else {\n // according to MDN buttons only reports up to button 5 (AUX2)\n but = ev.buttons & 1 ? CoreMouseButton.LEFT :\n ev.buttons & 4 ? CoreMouseButton.MIDDLE :\n ev.buttons & 2 ? CoreMouseButton.RIGHT :\n CoreMouseButton.NONE; // fallback to NONE\n }\n break;\n case 'mouseup':\n action = CoreMouseAction.UP;\n but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n break;\n case 'mousedown':\n action = CoreMouseAction.DOWN;\n but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;\n break;\n case 'wheel':\n if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {\n return false;\n }\n const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);\n\n if (amount === 0) {\n return false;\n }\n\n action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;\n but = CoreMouseButton.WHEEL;\n break;\n default:\n // dont handle other event types by accident\n return false;\n }\n\n // exit if we cannot determine valid button/action values\n // do nothing for higher buttons than wheel\n if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {\n return false;\n }\n\n return self.coreMouseService.triggerMouseEvent({\n col: pos.col,\n row: pos.row,\n x: pos.x,\n y: pos.y,\n button: but,\n action,\n ctrl: ev.ctrlKey,\n alt: ev.altKey,\n shift: ev.shiftKey\n });\n }\n\n /**\n * Event listener state handling.\n * We listen to the onProtocolChange event of CoreMouseService and put\n * requested listeners in `requestedEvents`. With this the listeners\n * have all bits to do the event listener juggling.\n * Note: 'mousedown' currently is \"always on\" and not managed\n * by onProtocolChange.\n */\n const requestedEvents: { [key: string]: ((ev: Event) => void) | null } = {\n mouseup: null,\n wheel: null,\n mousedrag: null,\n mousemove: null\n };\n const eventListeners: { [key: string]: (ev: any) => void | boolean } = {\n mouseup: (ev: MouseEvent) => {\n sendEvent(ev);\n if (!ev.buttons) {\n // if no other button is held remove global handlers\n this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);\n if (requestedEvents.mousedrag) {\n this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);\n }\n }\n return this.cancel(ev);\n },\n wheel: (ev: WheelEvent) => {\n sendEvent(ev);\n return this.cancel(ev, true);\n },\n mousedrag: (ev: MouseEvent) => {\n // deal only with move while a button is held\n if (ev.buttons) {\n sendEvent(ev);\n }\n },\n mousemove: (ev: MouseEvent) => {\n // deal only with move without any button\n if (!ev.buttons) {\n sendEvent(ev);\n }\n }\n };\n this.register(this.coreMouseService.onProtocolChange(events => {\n // apply global changes on events\n if (events) {\n if (this.optionsService.rawOptions.logLevel === 'debug') {\n this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events));\n }\n this.element!.classList.add('enable-mouse-events');\n this._selectionService!.disable();\n } else {\n this._logService.debug('Unbinding from mouse events.');\n this.element!.classList.remove('enable-mouse-events');\n this._selectionService!.enable();\n }\n\n // add/remove handlers from requestedEvents\n\n if (!(events & CoreMouseEventType.MOVE)) {\n el.removeEventListener('mousemove', requestedEvents.mousemove!);\n requestedEvents.mousemove = null;\n } else if (!requestedEvents.mousemove) {\n el.addEventListener('mousemove', eventListeners.mousemove);\n requestedEvents.mousemove = eventListeners.mousemove;\n }\n\n if (!(events & CoreMouseEventType.WHEEL)) {\n el.removeEventListener('wheel', requestedEvents.wheel!);\n requestedEvents.wheel = null;\n } else if (!requestedEvents.wheel) {\n el.addEventListener('wheel', eventListeners.wheel, { passive: false });\n requestedEvents.wheel = eventListeners.wheel;\n }\n\n if (!(events & CoreMouseEventType.UP)) {\n this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);\n requestedEvents.mouseup = null;\n } else if (!requestedEvents.mouseup) {\n requestedEvents.mouseup = eventListeners.mouseup;\n }\n\n if (!(events & CoreMouseEventType.DRAG)) {\n this._document!.removeEventListener('mousemove', requestedEvents.mousedrag!);\n requestedEvents.mousedrag = null;\n } else if (!requestedEvents.mousedrag) {\n requestedEvents.mousedrag = eventListeners.mousedrag;\n }\n }));\n // force initial onProtocolChange so we dont miss early mouse requests\n this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol;\n\n /**\n * \"Always on\" event listeners.\n */\n this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => {\n ev.preventDefault();\n this.focus();\n\n // Don't send the mouse button to the pty if mouse events are disabled or\n // if the selection manager is having selection forced (ie. a modifier is\n // held).\n if (!this.coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) {\n return;\n }\n\n sendEvent(ev);\n\n // Register additional global handlers which should keep reporting outside\n // of the terminal element.\n // Note: Other emulators also do this for 'mousedown' while a button\n // is held, we currently limit 'mousedown' to the terminal only.\n if (requestedEvents.mouseup) {\n this._document!.addEventListener('mouseup', requestedEvents.mouseup);\n }\n if (requestedEvents.mousedrag) {\n this._document!.addEventListener('mousemove', requestedEvents.mousedrag);\n }\n\n return this.cancel(ev);\n }));\n\n this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {\n // do nothing, if app side handles wheel itself\n if (requestedEvents.wheel) return;\n\n if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {\n return false;\n }\n\n if (!this.buffer.hasScrollback) {\n // Convert wheel events into up/down events when the buffer does not have scrollback, this\n // enables scrolling in apps hosted in the alt buffer such as vim or tmux.\n const amount = this.viewport!.getLinesScrolled(ev);\n\n // Do nothing if there's no vertical scroll\n if (amount === 0) {\n return;\n }\n\n // Construct and send sequences\n const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');\n let data = '';\n for (let i = 0; i < Math.abs(amount); i++) {\n data += sequence;\n }\n this.coreService.triggerDataEvent(data, true);\n return this.cancel(ev, true);\n }\n\n // normal viewport scrolling\n // conditionally stop event, if the viewport still had rows to scroll within\n if (this.viewport!.handleWheel(ev)) {\n return this.cancel(ev);\n }\n }, { passive: false }));\n\n this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {\n if (this.coreMouseService.areMouseEventsActive) return;\n this.viewport!.handleTouchStart(ev);\n return this.cancel(ev);\n }, { passive: true }));\n\n this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {\n if (this.coreMouseService.areMouseEventsActive) return;\n if (!this.viewport!.handleTouchMove(ev)) {\n return this.cancel(ev);\n }\n }, { passive: false }));\n }\n\n\n /**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param start The row to start from (between 0 and this.rows - 1).\n * @param end The row to end at (between start and this.rows - 1).\n */\n public refresh(start: number, end: number): void {\n this._renderService?.refreshRows(start, end);\n }\n\n /**\n * Change the cursor style for different selection modes\n */\n public updateCursorStyle(ev: KeyboardEvent | MouseEvent): void {\n if (this._selectionService?.shouldColumnSelect(ev)) {\n this.element!.classList.add('column-select');\n } else {\n this.element!.classList.remove('column-select');\n }\n }\n\n /**\n * Display the cursor element\n */\n private _showCursor(): void {\n if (!this.coreService.isCursorInitialized) {\n this.coreService.isCursorInitialized = true;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n }\n\n public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void {\n if (source === ScrollSource.VIEWPORT) {\n super.scrollLines(disp, suppressScrollEvent, source);\n this.refresh(0, this.rows - 1);\n } else {\n this.viewport?.scrollLines(disp);\n }\n }\n\n public paste(data: string): void {\n paste(data, this.textarea!, this.coreService, this.optionsService);\n }\n\n public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {\n this._customKeyEventHandler = customKeyEventHandler;\n }\n\n public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {\n this._customWheelEventHandler = customWheelEventHandler;\n }\n\n public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {\n return this._linkProviderService.registerLinkProvider(linkProvider);\n }\n\n public registerCharacterJoiner(handler: CharacterJoinerHandler): number {\n if (!this._characterJoinerService) {\n throw new Error('Terminal must be opened first');\n }\n const joinerId = this._characterJoinerService.register(handler);\n this.refresh(0, this.rows - 1);\n return joinerId;\n }\n\n public deregisterCharacterJoiner(joinerId: number): void {\n if (!this._characterJoinerService) {\n throw new Error('Terminal must be opened first');\n }\n if (this._characterJoinerService.deregister(joinerId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n\n public get markers(): IMarker[] {\n return this.buffer.markers;\n }\n\n public registerMarker(cursorYOffset: number): IMarker {\n return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);\n }\n\n public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {\n return this._decorationService.registerDecoration(decorationOptions);\n }\n\n /**\n * Gets whether the terminal has an active selection.\n */\n public hasSelection(): boolean {\n return this._selectionService ? this._selectionService.hasSelection : false;\n }\n\n /**\n * Selects text within the terminal.\n * @param column The column the selection starts at..\n * @param row The row the selection starts at.\n * @param length The length of the selection.\n */\n public select(column: number, row: number, length: number): void {\n this._selectionService!.setSelection(column, row, length);\n }\n\n /**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\n public getSelection(): string {\n return this._selectionService ? this._selectionService.selectionText : '';\n }\n\n public getSelectionPosition(): IBufferRange | undefined {\n if (!this._selectionService || !this._selectionService.hasSelection) {\n return undefined;\n }\n\n return {\n start: {\n x: this._selectionService.selectionStart![0],\n y: this._selectionService.selectionStart![1]\n },\n end: {\n x: this._selectionService.selectionEnd![0],\n y: this._selectionService.selectionEnd![1]\n }\n };\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._selectionService?.clearSelection();\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._selectionService?.selectAll();\n }\n\n public selectLines(start: number, end: number): void {\n this._selectionService?.selectLines(start, end);\n }\n\n /**\n * Handle a keydown [KeyboardEvent].\n *\n * [KeyboardEvent]: https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n */\n protected _keyDown(event: KeyboardEvent): boolean | undefined {\n this._keyDownHandled = false;\n this._keyDownSeen = true;\n\n if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {\n return false;\n }\n\n // Ignore composing with Alt key on Mac when macOptionIsMeta is enabled\n const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey;\n\n if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) {\n if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n if (!shouldIgnoreComposition && (event.key === 'Dead' || event.key === 'AltGraph')) {\n this._unprocessedDeadKey = true;\n }\n\n const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);\n\n this.updateCursorStyle(event);\n\n if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {\n const scrollCount = this.rows - 1;\n this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);\n return this.cancel(event, true);\n }\n\n if (result.type === KeyboardResultType.SELECT_ALL) {\n this.selectAll();\n }\n\n if (this._isThirdLevelShift(this.browser, event)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(event, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n // HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case\n // letters cannot be input while caps lock is on.\n if (event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {\n if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {\n return true;\n }\n }\n\n if (this._unprocessedDeadKey) {\n this._unprocessedDeadKey = false;\n return true;\n }\n\n // If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers\n // will announce deleted characters. This will not work 100% of the time but it should cover\n // most scenarios.\n if (result.key === C0.ETX || result.key === C0.CR) {\n this.textarea!.value = '';\n }\n\n this._onKey.fire({ key: result.key, domEvent: event });\n this._showCursor();\n this.coreService.triggerDataEvent(result.key, true);\n\n // Cancel events when not in screen reader mode so events don't get bubbled up and handled by\n // other listeners. When screen reader mode is enabled, we don't cancel them (unless ctrl or alt\n // is also depressed) so that the cursor textarea can be updated, which triggers the screen\n // reader to read it.\n if (!this.optionsService.rawOptions.screenReaderMode || event.altKey || event.ctrlKey) {\n return this.cancel(event, true);\n }\n\n this._keyDownHandled = true;\n }\n\n private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {\n const thirdLevelKey =\n (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey) ||\n (browser.isWindows && ev.getModifierState('AltGraph'));\n\n if (ev.type === 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n }\n\n protected _keyUp(ev: KeyboardEvent): void {\n this._keyDownSeen = false;\n\n if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {\n return;\n }\n\n if (!wasModifierKeyOnlyEvent(ev)) {\n this.focus();\n }\n\n this.updateCursorStyle(ev);\n this._keyPressHandled = false;\n }\n\n /**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param ev The keypress event to be handled.\n */\n protected _keyPress(ev: KeyboardEvent): boolean {\n let key;\n\n this._keyPressHandled = false;\n\n if (this._keyDownHandled) {\n return false;\n }\n\n if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which === null || ev.which === undefined) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this._onKey.fire({ key, domEvent: ev });\n this._showCursor();\n this.coreService.triggerDataEvent(key, true);\n\n this._keyPressHandled = true;\n\n // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow\n // keys could be ignored\n this._unprocessedDeadKey = false;\n\n return true;\n }\n\n /**\n * Handle an input event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/Web/API/InputEvent\n * @param ev The input event to be handled.\n */\n protected _inputEvent(ev: InputEvent): boolean {\n // Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to\n // support reading out character input which can doubling up input characters\n // Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679\n if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {\n if (this._keyPressHandled) {\n return false;\n }\n\n // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow\n // keys could be ignored\n this._unprocessedDeadKey = false;\n\n const text = ev.data;\n this.coreService.triggerDataEvent(text, true);\n\n this.cancel(ev);\n return true;\n }\n\n return false;\n }\n\n /**\n * Resizes the terminal.\n *\n * @param x The number of columns to resize to.\n * @param y The number of rows to resize to.\n */\n public resize(x: number, y: number): void {\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (this._charSizeService && !this._charSizeService.hasValidSize) {\n this._charSizeService.measure();\n }\n return;\n }\n\n super.resize(x, y);\n }\n\n private _afterResize(x: number, y: number): void {\n this._charSizeService?.measure();\n\n // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an\n // invalid location\n this.viewport?.syncScrollArea(true);\n }\n\n /**\n * Clear the entire buffer, making the prompt line the new first line.\n */\n public clear(): void {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.clearAllMarkers();\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (let i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n }\n // IMPORTANT: Fire scroll event before viewport is reset. This ensures embedders get the clear\n // scroll event and that the viewport's state will be valid for immediate writes.\n this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL });\n this.viewport?.reset();\n this.refresh(0, this.rows - 1);\n }\n\n /**\n * Reset terminal.\n * Note: Calling this directly from JS is synchronous but does not clear\n * input buffers and does not reset the parser, thus the terminal will\n * continue to apply pending input data.\n * If you need in band reset (synchronous with input data) consider\n * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).\n */\n public reset(): void {\n /**\n * Since _setup handles a full terminal creation, we have to carry forward\n * a few things that should not reset.\n */\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n const customKeyEventHandler = this._customKeyEventHandler;\n\n this._setup();\n super.reset();\n this._selectionService?.reset();\n this._decorationService.reset();\n this.viewport?.reset();\n\n // reattach\n this._customKeyEventHandler = customKeyEventHandler;\n\n // do a full screen refresh\n this.refresh(0, this.rows - 1);\n }\n\n public clearTextureAtlas(): void {\n this._renderService?.clearTextureAtlas();\n }\n\n private _reportFocus(): void {\n if (this.element?.classList.contains('focus')) {\n this.coreService.triggerDataEvent(C0.ESC + '[I');\n } else {\n this.coreService.triggerDataEvent(C0.ESC + '[O');\n }\n }\n\n private _reportWindowsOptions(type: WindowsOptionsReportType): void {\n if (!this._renderService) {\n return;\n }\n\n switch (type) {\n case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:\n const canvasWidth = this._renderService.dimensions.css.canvas.width.toFixed(0);\n const canvasHeight = this._renderService.dimensions.css.canvas.height.toFixed(0);\n this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);\n break;\n case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:\n const cellWidth = this._renderService.dimensions.css.cell.width.toFixed(0);\n const cellHeight = this._renderService.dimensions.css.cell.height.toFixed(0);\n this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);\n break;\n }\n }\n\n // TODO: Remove cancel function and cancelEvents option\n public cancel(ev: Event, force?: boolean): boolean | undefined {\n if (!this.options.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n }\n}\n\n/**\n * Helpers\n */\n\nfunction wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nconst RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second\n\nimport { IRenderDebouncer } from 'browser/Types';\n\n/**\n * Debounces calls to update screen readers to update at most once configurable interval of time.\n */\nexport class TimeBasedDebouncer implements IRenderDebouncer {\n private _rowStart: number | undefined;\n private _rowEnd: number | undefined;\n private _rowCount: number | undefined;\n\n // The last moment that the Terminal was refreshed at\n private _lastRefreshMs = 0;\n // Whether a trailing refresh should be triggered due to a refresh request that was throttled\n private _additionalRefreshRequested = false;\n\n private _refreshTimeoutID: number | undefined;\n\n constructor(\n private _renderCallback: (start: number, end: number) => void,\n private readonly _debounceThresholdMS = RENDER_DEBOUNCE_THRESHOLD_MS\n ) {\n }\n\n public dispose(): void {\n if (this._refreshTimeoutID) {\n clearTimeout(this._refreshTimeoutID);\n }\n }\n\n public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {\n this._rowCount = rowCount;\n // Get the min/max row start/end for the arg values\n rowStart = rowStart !== undefined ? rowStart : 0;\n rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;\n // Set the properties to the updated values\n this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;\n this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;\n\n // Only refresh if the time since last refresh is above a threshold, otherwise wait for\n // enough time to pass before refreshing again.\n const refreshRequestTime: number = Date.now();\n if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {\n // Enough time has lapsed since the last refresh; refresh immediately\n this._lastRefreshMs = refreshRequestTime;\n this._innerRefresh();\n } else if (!this._additionalRefreshRequested) {\n // This is the first additional request throttled; set up trailing refresh\n const elapsed = refreshRequestTime - this._lastRefreshMs;\n const waitPeriodBeforeTrailingRefresh = this._debounceThresholdMS - elapsed;\n this._additionalRefreshRequested = true;\n\n this._refreshTimeoutID = window.setTimeout(() => {\n this._lastRefreshMs = Date.now();\n this._innerRefresh();\n this._additionalRefreshRequested = false;\n this._refreshTimeoutID = undefined; // No longer need to clear the timeout\n }, waitPeriodBeforeTrailingRefresh);\n }\n }\n\n private _innerRefresh(): void {\n // Make sure values are set\n if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {\n return;\n }\n\n // Clamp values\n const start = Math.max(this._rowStart, 0);\n const end = Math.min(this._rowEnd, this._rowCount - 1);\n\n // Reset debouncer (this happens before render callback as the render could trigger it again)\n this._rowStart = undefined;\n this._rowEnd = undefined;\n\n // Run render callback\n this._renderCallback(start, end);\n }\n}\n\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { addDisposableDomListener } from 'browser/Lifecycle';\nimport { IViewport, ReadonlyColorSet } from 'browser/Types';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';\nimport { EventEmitter } from 'common/EventEmitter';\nimport { Disposable } from 'common/Lifecycle';\nimport { IBuffer } from 'common/buffer/Types';\nimport { IBufferService, IOptionsService } from 'common/services/Services';\n\nconst FALLBACK_SCROLL_BAR_WIDTH = 15;\n\ninterface ISmoothScrollState {\n startTime: number;\n origin: number;\n target: number;\n}\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport extends Disposable implements IViewport {\n public scrollBarWidth: number = 0;\n private _currentRowHeight: number = 0;\n private _currentDeviceCellHeight: number = 0;\n private _lastRecordedBufferLength: number = 0;\n private _lastRecordedViewportHeight: number = 0;\n private _lastRecordedBufferHeight: number = 0;\n private _lastTouchY: number = 0;\n private _lastScrollTop: number = 0;\n private _activeBuffer: IBuffer;\n private _renderDimensions: IRenderDimensions;\n\n // Stores a partial line amount when scrolling, this is used to keep track of how much of a line\n // is scrolled so we can \"scroll\" over partial lines and feel natural on touchpads. This is a\n // quick fix and could have a more robust solution in place that reset the value when needed.\n private _wheelPartialScroll: number = 0;\n\n private _refreshAnimationFrame: number | null = null;\n private _ignoreNextScrollEvent: boolean = false;\n private _smoothScrollState: ISmoothScrollState = {\n startTime: 0,\n origin: -1,\n target: -1\n };\n\n private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>());\n public readonly onRequestScrollLines = this._onRequestScrollLines.event;\n\n constructor(\n private readonly _viewportElement: HTMLElement,\n private readonly _scrollArea: HTMLElement,\n @IBufferService private readonly _bufferService: IBufferService,\n @IOptionsService private readonly _optionsService: IOptionsService,\n @ICharSizeService private readonly _charSizeService: ICharSizeService,\n @IRenderService private readonly _renderService: IRenderService,\n @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n @IThemeService themeService: IThemeService\n ) {\n super();\n\n // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.\n // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that\n // case, therefore we account for a standard amount to make it visible\n this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;\n this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._handleScroll.bind(this)));\n\n // Track properties used in performance critical code manually to avoid using slow getters\n this._activeBuffer = this._bufferService.buffer;\n this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));\n this._renderDimensions = this._renderService.dimensions;\n this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));\n\n this._handleThemeChange(themeService.colors);\n this.register(themeService.onChangeColors(e => this._handleThemeChange(e)));\n this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea()));\n\n // Perform this async to ensure the ICharSizeService is ready.\n setTimeout(() => this.syncScrollArea());\n }\n\n private _handleThemeChange(colors: ReadonlyColorSet): void {\n this._viewportElement.style.backgroundColor = colors.background.css;\n }\n\n public reset(): void {\n this._currentRowHeight = 0;\n this._currentDeviceCellHeight = 0;\n this._lastRecordedBufferLength = 0;\n this._lastRecordedViewportHeight = 0;\n this._lastRecordedBufferHeight = 0;\n this._lastTouchY = 0;\n this._lastScrollTop = 0;\n // Sync on next animation frame to ensure the new terminal state is used\n this._coreBrowserService.window.requestAnimationFrame(() => this.syncScrollArea());\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private _refresh(immediate: boolean): void {\n if (immediate) {\n this._innerRefresh();\n if (this._refreshAnimationFrame !== null) {\n this._coreBrowserService.window.cancelAnimationFrame(this._refreshAnimationFrame);\n }\n return;\n }\n if (this._refreshAnimationFrame === null) {\n this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());\n }\n }\n\n private _innerRefresh(): void {\n if (this._charSizeService.height > 0) {\n this._currentRowHeight = this._renderDimensions.device.cell.height / this._coreBrowserService.dpr;\n this._currentDeviceCellHeight = this._renderDimensions.device.cell.height;\n this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;\n const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderDimensions.css.canvas.height);\n if (this._lastRecordedBufferHeight !== newBufferHeight) {\n this._lastRecordedBufferHeight = newBufferHeight;\n this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';\n }\n }\n\n // Sync scrollTop\n const scrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;\n if (this._viewportElement.scrollTop !== scrollTop) {\n // Ignore the next scroll event which will be triggered by setting the scrollTop as we do not\n // want this event to scroll the terminal\n this._ignoreNextScrollEvent = true;\n this._viewportElement.scrollTop = scrollTop;\n }\n\n this._refreshAnimationFrame = null;\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(immediate: boolean = false): void {\n // If buffer height changed\n if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {\n this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;\n this._refresh(immediate);\n return;\n }\n\n // If viewport height changed\n if (this._lastRecordedViewportHeight !== this._renderService.dimensions.css.canvas.height) {\n this._refresh(immediate);\n return;\n }\n\n // If the buffer position doesn't match last scroll top\n if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {\n this._refresh(immediate);\n return;\n }\n\n // If row height changed\n if (this._renderDimensions.device.cell.height !== this._currentDeviceCellHeight) {\n this._refresh(immediate);\n return;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private _handleScroll(ev: Event): void {\n // Record current scroll top position\n this._lastScrollTop = this._viewportElement.scrollTop;\n\n // Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt\n // which causes the terminal to scroll the buffer to the top\n if (!this._viewportElement.offsetParent) {\n return;\n }\n\n // Ignore the event if it was flagged to ignore (when the source of the event is from Viewport)\n if (this._ignoreNextScrollEvent) {\n this._ignoreNextScrollEvent = false;\n // Still trigger the scroll so lines get refreshed\n this._onRequestScrollLines.fire({ amount: 0, suppressScrollEvent: true });\n return;\n }\n\n const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);\n const diff = newRow - this._bufferService.buffer.ydisp;\n this._onRequestScrollLines.fire({ amount: diff, suppressScrollEvent: true });\n }\n\n private _smoothScroll(): void {\n // Check valid state\n if (this._isDisposed || this._smoothScrollState.origin === -1 || this._smoothScrollState.target === -1) {\n return;\n }\n\n // Calculate position complete\n const percent = this._smoothScrollPercent();\n this._viewportElement.scrollTop = this._smoothScrollState.origin + Math.round(percent * (this._smoothScrollState.target - this._smoothScrollState.origin));\n\n // Continue or finish smooth scroll\n if (percent < 1) {\n this._coreBrowserService.window.requestAnimationFrame(() => this._smoothScroll());\n } else {\n this._clearSmoothScrollState();\n }\n }\n\n private _smoothScrollPercent(): number {\n if (!this._optionsService.rawOptions.smoothScrollDuration || !this._smoothScrollState.startTime) {\n return 1;\n }\n return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollDuration, 1), 0);\n }\n\n private _clearSmoothScrollState(): void {\n this._smoothScrollState.startTime = 0;\n this._smoothScrollState.origin = -1;\n this._smoothScrollState.target = -1;\n }\n\n /**\n * Handles bubbling of scroll event in case the viewport has reached top or bottom\n * @param ev The scroll event.\n * @param amount The amount scrolled\n */\n private _bubbleScroll(ev: Event, amount: number): boolean {\n const scrollPosFromTop = this._viewportElement.scrollTop + this._lastRecordedViewportHeight;\n if ((amount < 0 && this._viewportElement.scrollTop !== 0) ||\n (amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {\n if (ev.cancelable) {\n ev.preventDefault();\n }\n return false;\n }\n return true;\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public handleWheel(ev: WheelEvent): boolean {\n const amount = this._getPixelsScrolled(ev);\n if (amount === 0) {\n return false;\n }\n if (!this._optionsService.rawOptions.smoothScrollDuration) {\n this._viewportElement.scrollTop += amount;\n } else {\n this._smoothScrollState.startTime = Date.now();\n if (this._smoothScrollPercent() < 1) {\n this._smoothScrollState.origin = this._viewportElement.scrollTop;\n if (this._smoothScrollState.target === -1) {\n this._smoothScrollState.target = this._viewportElement.scrollTop + amount;\n } else {\n this._smoothScrollState.target += amount;\n }\n this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0);\n this._smoothScroll();\n } else {\n this._clearSmoothScrollState();\n }\n }\n return this._bubbleScroll(ev, amount);\n }\n\n public scrollLines(disp: number): void {\n if (disp === 0) {\n return;\n }\n if (!this._optionsService.rawOptions.smoothScrollDuration) {\n this._onRequestScrollLines.fire({ amount: disp, suppressScrollEvent: false });\n } else {\n const amount = disp * this._currentRowHeight;\n this._smoothScrollState.startTime = Date.now();\n if (this._smoothScrollPercent() < 1) {\n this._smoothScrollState.origin = this._viewportElement.scrollTop;\n this._smoothScrollState.target = this._smoothScrollState.origin + amount;\n this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0);\n this._smoothScroll();\n } else {\n this._clearSmoothScrollState();\n }\n }\n }\n\n private _getPixelsScrolled(ev: WheelEvent): number {\n // Do nothing if it's not a vertical scroll event\n if (ev.deltaY === 0 || ev.shiftKey) {\n return 0;\n }\n\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let amount = this._applyScrollModifier(ev.deltaY, ev);\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n amount *= this._currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n amount *= this._currentRowHeight * this._bufferService.rows;\n }\n return amount;\n }\n\n\n public getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement } {\n let currentLine: string = '';\n let cursorElement: HTMLElement | undefined;\n const bufferElements: HTMLElement[] = [];\n const end = endLine ?? this._bufferService.buffer.lines.length;\n const lines = this._bufferService.buffer.lines;\n for (let i = startLine; i < end; i++) {\n const line = lines.get(i);\n if (!line) {\n continue;\n }\n const isWrapped = lines.get(i + 1)?.isWrapped;\n currentLine += line.translateToString(!isWrapped);\n if (!isWrapped || i === lines.length - 1) {\n const div = document.createElement('div');\n div.textContent = currentLine;\n bufferElements.push(div);\n if (currentLine.length > 0) {\n cursorElement = div;\n }\n currentLine = '';\n }\n }\n return { bufferElements, cursorElement };\n }\n\n /**\n * Gets the number of pixels scrolled by the mouse event taking into account what type of delta\n * is being used.\n * @param ev The mouse wheel event.\n */\n public getLinesScrolled(ev: WheelEvent): number {\n // Do nothing if it's not a vertical scroll event\n if (ev.deltaY === 0 || ev.shiftKey) {\n return 0;\n }\n\n // Fallback to WheelEvent.DOM_DELTA_LINE\n let amount = this._applyScrollModifier(ev.deltaY, ev);\n if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {\n amount /= this._currentRowHeight + 0.0; // Prevent integer division\n this._wheelPartialScroll += amount;\n amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);\n this._wheelPartialScroll %= 1;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n amount *= this._bufferService.rows;\n }\n return amount;\n }\n\n private _applyScrollModifier(amount: number, ev: WheelEvent): number {\n const modifier = this._optionsService.rawOptions.fastScrollModifier;\n // Multiply the scroll speed when the modifier is down\n if ((modifier === 'alt' && ev.altKey) ||\n (modifier === 'ctrl' && ev.ctrlKey) ||\n (modifier === 'shift' && ev.shiftKey)) {\n return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;\n }\n\n return amount * this._optionsService.rawOptions.scrollSensitivity;\n }\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public handleTouchStart(ev: TouchEvent): void {\n this._lastTouchY = ev.touches[0].pageY;\n }\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public handleTouchMove(ev: TouchEvent): boolean {\n const deltaY = this._lastTouchY - ev.touches[0].pageY;\n this._lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return false;\n }\n this._viewportElement.scrollTop += deltaY;\n return this._bubbleScroll(ev, deltaY);\n }\n}\n","/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { ICoreBrowserService, IRenderService } from 'browser/services/Services';\nimport { Disposable, toDisposable } from 'common/Lifecycle';\nimport { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';\n\nexport class BufferDecorationRenderer extends Disposable {\n private readonly _container: HTMLElement;\n private readonly _decorationElements: Map = new Map();\n\n private _animationFrame: number | undefined;\n private _altBufferIsActive: boolean = false;\n private _dimensionsChanged: boolean = false;\n\n constructor(\n private readonly _screenElement: HTMLElement,\n @IBufferService private readonly _bufferService: IBufferService,\n @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,\n @IDecorationService private readonly _decorationService: IDecorationService,\n @IRenderService private readonly _renderService: IRenderService\n ) {\n super();\n\n this._container = document.createElement('div');\n this._container.classList.add('xterm-decoration-container');\n this._screenElement.appendChild(this._container);\n\n this.register(this._renderService.onRenderedViewportChange(() => this._doRefreshDecorations()));\n this.register(this._renderService.onDimensionsChange(() => {\n this._dimensionsChanged = true;\n this._queueRefresh();\n }));\n this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh()));\n this.register(this._bufferService.buffers.onBufferActivate(() => {\n this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;\n }));\n this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));\n this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));\n this.register(toDisposable(() => {\n this._container.remove();\n this._decorationElements.clear();\n }));\n }\n\n private _queueRefresh(): void {\n if (this._animationFrame !== undefined) {\n return;\n }\n this._animationFrame = this._renderService.addRefreshCallback(() => {\n this._doRefreshDecorations();\n this._animationFrame = undefined;\n });\n }\n\n private _doRefreshDecorations(): void {\n for (const decoration of this._decorationService.decorations) {\n this._renderDecoration(decoration);\n }\n this._dimensionsChanged = false;\n }\n\n private _renderDecoration(decoration: IInternalDecoration): void {\n this._refreshStyle(decoration);\n if (this._dimensionsChanged) {\n this._refreshXPosition(decoration);\n }\n }\n\n private _createElement(decoration: IInternalDecoration): HTMLElement {\n const element = this._coreBrowserService.mainDocument.createElement('div');\n element.classList.add('xterm-decoration');\n element.classList.toggle('xterm-decoration-top-layer', decoration?.options?.layer === 'top');\n element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`;\n element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.css.cell.height}px`;\n element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.css.cell.height}px`;\n element.style.lineHeight = `${this._renderService.dimensions.css.cell.height}px`;\n\n const x = decoration.options.x ?? 0;\n if (x && x > this._bufferService.cols) {\n // exceeded the container width, so hide\n element.style.display = 'none';\n }\n this._refreshXPosition(decoration, element);\n\n return element;\n }\n\n private _refreshStyle(decoration: IInternalDecoration): void {\n const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;\n if (line < 0 || line >= this._bufferService.rows) {\n // outside of viewport\n if (decoration.element) {\n decoration.element.style.display = 'none';\n decoration.onRenderEmitter.fire(decoration.element);\n }\n } else {\n let element = this._decorationElements.get(decoration);\n if (!element) {\n element = this._createElement(decoration);\n decoration.element = element;\n this._decorationElements.set(decoration, element);\n this._container.appendChild(element);\n decoration.onDispose(() => {\n this._decorationElements.delete(decoration);\n element!.remove();\n });\n }\n element.style.top = `${line * this._renderService.dimensions.css.cell.height}px`;\n element.style.display = this._altBufferIsActive ? 'none' : 'block';\n decoration.onRenderEmitter.fire(element);\n }\n }\n\n private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement | undefined = decoration.element): void {\n if (!element) {\n return;\n }\n const x = decoration.options.x ?? 0;\n if ((decoration.options.anchor || 'left') === 'right') {\n element.style.right = x ? `${x * this._renderService.dimensions.css.cell.width}px` : '';\n } else {\n element.style.left = x ? `${x * this._renderService.dimensions.css.cell.width}px` : '';\n }\n }\n\n private _removeDecoration(decoration: IInternalDecoration): void {\n this._decorationElements.get(decoration)?.remove();\n this._decorationElements.delete(decoration);\n decoration.dispose();\n }\n}\n","/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { IInternalDecoration } from 'common/services/Services';\n\nexport interface IColorZoneStore {\n readonly zones: IColorZone[];\n clear(): void;\n addDecoration(decoration: IInternalDecoration): void;\n /**\n * Sets the amount of padding in lines that will be added between zones, if new lines intersect\n * the padding they will be merged into the same zone.\n */\n setPadding(padding: { [position: string]: number }): void;\n}\n\nexport interface IColorZone {\n /** Color in a format supported by canvas' fillStyle. */\n color: string;\n position: 'full' | 'left' | 'center' | 'right' | undefined;\n startBufferLine: number;\n endBufferLine: number;\n}\n\ninterface IMinimalDecorationForColorZone {\n marker: Pick