mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-14 16:40:05 +00:00
bumped xterm
This commit is contained in:
parent
2505ee89d0
commit
589b7ea46c
@ -31,8 +31,10 @@
|
||||
"runes": "^0.4.2",
|
||||
"slug": "^1.1.0",
|
||||
"uuid": "^3.3.2",
|
||||
"xterm": "https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.1.tgz",
|
||||
"xterm-addon-ligatures": "^0.1.0-beta-2"
|
||||
"xterm": "https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.2.tgz",
|
||||
"xterm-addon-fit": "^0.1.0-beta3",
|
||||
"xterm-addon-ligatures": "^0.1.0-beta-2",
|
||||
"xterm-addon-search": "^0.1.0-beta6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "4.0.1",
|
||||
|
@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
interface ITerminalDimensions {
|
||||
/**
|
||||
* The number of rows in the terminal.
|
||||
*/
|
||||
rows: number;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
*/
|
||||
cols: number;
|
||||
}
|
||||
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public dispose(): void {}
|
||||
|
||||
public fit(): void {
|
||||
const dims = this.proposeDimensions();
|
||||
if (!dims || !this._terminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (<any>this._terminal)._core;
|
||||
|
||||
// Force a full render
|
||||
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
|
||||
core._renderCoordinator.clear();
|
||||
this._terminal.resize(dims.cols, dims.rows);
|
||||
}
|
||||
}
|
||||
|
||||
public proposeDimensions(): ITerminalDimensions | undefined {
|
||||
if (!this._terminal) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._terminal.element.parentElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (<any>this._terminal)._core;
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
const elementStyle = window.getComputedStyle(this._terminal.element);
|
||||
const elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
|
||||
const geometry = {
|
||||
cols: Math.floor(availableWidth / core._renderCoordinator.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / core._renderCoordinator.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { Frontend } from './frontend'
|
||||
import { Terminal, ITheme } from 'xterm'
|
||||
import { getCSSFontFamily } from '../utils'
|
||||
import { FitAddon } from './xtermAddonFit'
|
||||
import { FitAddon } from 'xterm-addon-fit'
|
||||
import { enableLigatures } from 'xterm-addon-ligatures'
|
||||
import { SearchAddon, ISearchOptions } from './xtermSearchAddon'
|
||||
import { SearchAddon, ISearchOptions } from 'xterm-addon-search'
|
||||
import './xterm.css'
|
||||
import deepEqual = require('deep-equal')
|
||||
import { Attributes, AttributeData, CellData } from 'xterm/src/core/buffer/BufferLine'
|
||||
import { Attributes, AttributeData, CellData } from 'xterm/src/common/buffer/BufferLine'
|
||||
|
||||
const COLOR_NAMES = [
|
||||
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
|
||||
@ -83,8 +83,9 @@ export class XTermFrontend extends Frontend {
|
||||
this.resizeHandler = () => {
|
||||
try {
|
||||
this.fitAddon.fit()
|
||||
} catch {
|
||||
} catch (e) {
|
||||
// tends to throw when element wasn't shown yet
|
||||
console.warn('Could not resize xterm', e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,328 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm'
|
||||
|
||||
export interface ISearchOptions {
|
||||
regex?: boolean
|
||||
wholeWord?: boolean
|
||||
caseSensitive?: boolean
|
||||
incremental?: boolean
|
||||
}
|
||||
|
||||
export interface ISearchResult {
|
||||
term: string
|
||||
col: number
|
||||
row: number
|
||||
}
|
||||
|
||||
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?'
|
||||
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000 // 15 secs
|
||||
|
||||
export class SearchAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined
|
||||
|
||||
private _linesCache: string[] | undefined
|
||||
private _linesCacheTimeoutId = 0
|
||||
private _cursorMoveListener: IDisposable | undefined
|
||||
private _resizeListener: IDisposable | undefined
|
||||
|
||||
public activate (terminal: Terminal): void {
|
||||
this._terminal = terminal
|
||||
}
|
||||
|
||||
public dispose (): void {}
|
||||
|
||||
public findNext (term: string, searchOptions?: ISearchOptions): boolean {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded')
|
||||
}
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection()
|
||||
return false
|
||||
}
|
||||
|
||||
let startCol: number = 0
|
||||
let startRow = this._terminal.buffer.viewportY
|
||||
|
||||
if (this._terminal.hasSelection()) {
|
||||
const incremental = searchOptions ? searchOptions.incremental : false
|
||||
// Start from the selection end if there is a selection
|
||||
// For incremental search, use existing row
|
||||
const currentSelection = this._terminal.getSelectionPosition()!
|
||||
startRow = incremental ? currentSelection.startRow : currentSelection.endRow
|
||||
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn
|
||||
}
|
||||
|
||||
this._initLinesCache()
|
||||
|
||||
// A row that has isWrapped = false
|
||||
let findingRow = startRow
|
||||
// index of beginning column that _findInLine need to scan.
|
||||
let cumulativeCols = startCol
|
||||
// If startRow is wrapped row, scan for unwrapped row above.
|
||||
// So we can start matching on wrapped line from long unwrapped line.
|
||||
let currentLine = this._terminal.buffer.getLine(findingRow)
|
||||
while (currentLine && currentLine.isWrapped) {
|
||||
cumulativeCols += this._terminal.cols
|
||||
currentLine = this._terminal.buffer.getLine(--findingRow)
|
||||
}
|
||||
|
||||
// Search startRow
|
||||
let result = this._findInLine(term, findingRow, cumulativeCols, searchOptions)
|
||||
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
|
||||
|
||||
// If the current line is wrapped line, increase index of column to ignore the previous scan
|
||||
// Otherwise, reset beginning column index to zero with set new unwrapped line index
|
||||
result = this._findInLine(term, y, 0, searchOptions)
|
||||
if (result) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the top to the startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
for (let y = 0; y < findingRow; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions)
|
||||
if (result) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result)
|
||||
}
|
||||
|
||||
public findPrevious (term: string, searchOptions?: ISearchOptions): boolean {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded')
|
||||
}
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection()
|
||||
return false
|
||||
}
|
||||
|
||||
const isReverseSearch = true
|
||||
let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1
|
||||
let startCol = this._terminal.cols
|
||||
|
||||
if (this._terminal.hasSelection()) {
|
||||
// Start from the selection start if there is a selection
|
||||
const currentSelection = this._terminal.getSelectionPosition()!
|
||||
startRow = currentSelection.startRow
|
||||
startCol = currentSelection.startColumn
|
||||
}
|
||||
|
||||
this._initLinesCache()
|
||||
|
||||
// Search startRow
|
||||
let result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch)
|
||||
|
||||
// Search from startRow - 1 to top
|
||||
if (!result) {
|
||||
// If the line is wrapped line, increase number of columns that is needed to be scanned
|
||||
// Se we can scan on wrapped line from unwrapped line
|
||||
let cumulativeCols = this._terminal.cols
|
||||
if (this._terminal.buffer.getLine(startRow)!.isWrapped) {
|
||||
cumulativeCols += startCol
|
||||
}
|
||||
for (let y = startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch)
|
||||
if (result) {
|
||||
break
|
||||
}
|
||||
// If the current line is wrapped line, increase scanning range,
|
||||
// preparing for scanning on unwrapped line
|
||||
const line = this._terminal.buffer.getLine(y)
|
||||
if (line && line.isWrapped) {
|
||||
cumulativeCols += this._terminal.cols
|
||||
} else {
|
||||
cumulativeCols = this._terminal.cols
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the bottom to startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1
|
||||
let cumulativeCols = this._terminal.cols
|
||||
for (let y = searchFrom; y >= startRow; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch)
|
||||
if (result) {
|
||||
break
|
||||
}
|
||||
const line = this._terminal.buffer.getLine(y)
|
||||
if (line && line.isWrapped) {
|
||||
cumulativeCols += this._terminal.cols
|
||||
} else {
|
||||
cumulativeCols = this._terminal.cols
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result)
|
||||
}
|
||||
|
||||
private _initLinesCache (): void {
|
||||
const terminal = this._terminal!
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(terminal.buffer.length)
|
||||
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache())
|
||||
this._resizeListener = terminal.onResize(() => this._destroyLinesCache())
|
||||
}
|
||||
|
||||
window.clearTimeout(this._linesCacheTimeoutId)
|
||||
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE)
|
||||
}
|
||||
|
||||
private _destroyLinesCache (): void {
|
||||
this._linesCache = undefined
|
||||
if (this._cursorMoveListener) {
|
||||
this._cursorMoveListener.dispose()
|
||||
this._cursorMoveListener = undefined
|
||||
}
|
||||
if (this._resizeListener) {
|
||||
this._resizeListener.dispose()
|
||||
this._resizeListener = undefined
|
||||
}
|
||||
if (this._linesCacheTimeoutId) {
|
||||
window.clearTimeout(this._linesCacheTimeoutId)
|
||||
this._linesCacheTimeoutId = 0
|
||||
}
|
||||
}
|
||||
|
||||
private _isWholeWord (searchIndex: number, line: string, term: string): boolean {
|
||||
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)))
|
||||
}
|
||||
|
||||
protected _findInLine (term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
|
||||
const terminal = this._terminal!
|
||||
|
||||
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
|
||||
const firstLine = terminal.buffer.getLine(row)
|
||||
if (firstLine && firstLine.isWrapped) {
|
||||
return null
|
||||
}
|
||||
let stringLine = this._linesCache ? this._linesCache[row] : void 0
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this._translateBufferLineToStringWithWrap(row, true)
|
||||
if (this._linesCache) {
|
||||
this._linesCache[row] = stringLine
|
||||
}
|
||||
}
|
||||
|
||||
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase()
|
||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase()
|
||||
|
||||
let resultIndex = -1
|
||||
if (searchOptions.regex) {
|
||||
const searchRegex = RegExp(searchTerm, 'g')
|
||||
let foundTerm: RegExpExecArray | null
|
||||
if (isReverseSearch) {
|
||||
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
|
||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length
|
||||
term = foundTerm[0]
|
||||
searchRegex.lastIndex -= (term.length - 1)
|
||||
}
|
||||
} else {
|
||||
foundTerm = searchRegex.exec(searchStringLine.slice(col))
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length)
|
||||
term = foundTerm[0]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isReverseSearch) {
|
||||
if (col - searchTerm.length >= 0) {
|
||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length)
|
||||
}
|
||||
} else {
|
||||
resultIndex = searchStringLine.indexOf(searchTerm, col)
|
||||
}
|
||||
}
|
||||
|
||||
if (resultIndex >= 0) {
|
||||
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
|
||||
if (resultIndex >= terminal.cols) {
|
||||
row += Math.floor(resultIndex / terminal.cols)
|
||||
resultIndex = resultIndex % terminal.cols
|
||||
}
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const line = terminal.buffer.getLine(row)
|
||||
|
||||
if (line) {
|
||||
for (let i = 0; i < resultIndex; i++) {
|
||||
const cell = line.getCell(i)
|
||||
if (!cell) {
|
||||
break
|
||||
}
|
||||
// Adjust the searchIndex to normalize emoji into single chars
|
||||
const char = cell.char
|
||||
if (char.length > 1) {
|
||||
resultIndex -= char.length - 1
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
const charWidth = cell.width
|
||||
if (charWidth === 0) {
|
||||
resultIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
term,
|
||||
col: resultIndex,
|
||||
row
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private _translateBufferLineToStringWithWrap (lineIndex: number, trimRight: boolean): string {
|
||||
const terminal = this._terminal!
|
||||
let lineString = ''
|
||||
let lineWrapsToNext: boolean
|
||||
|
||||
do {
|
||||
const nextLine = terminal.buffer.getLine(lineIndex + 1)
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false
|
||||
const line = terminal.buffer.getLine(lineIndex)
|
||||
if (!line) {
|
||||
break
|
||||
}
|
||||
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols)
|
||||
lineIndex++
|
||||
} while (lineWrapsToNext)
|
||||
|
||||
return lineString
|
||||
}
|
||||
|
||||
private _selectResult (result: ISearchResult | undefined): boolean {
|
||||
const terminal = this._terminal!
|
||||
if (!result) {
|
||||
terminal.clearSelection()
|
||||
return false
|
||||
}
|
||||
terminal.select(result.col, result.row, result.term.length)
|
||||
terminal.scrollLines(result.row - terminal.buffer.viewportY)
|
||||
return true
|
||||
}
|
||||
}
|
@ -246,6 +246,11 @@ windows-native-registry@^1.0.14:
|
||||
dependencies:
|
||||
nan "^2.13.2"
|
||||
|
||||
xterm-addon-fit@^0.1.0-beta3:
|
||||
version "0.1.0-beta3"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.1.0-beta3.tgz#82bc2474cb00a2872d9a43ea52ed801bdfeebeaf"
|
||||
integrity sha512-5UWwCH3smDWlqskK8r0GcCgLPvwbJiPpKiM01E67g5HIrQqCxITKawc01cuKCDLVj03sFJB9jDCOoh0n0mN+Zw==
|
||||
|
||||
xterm-addon-ligatures@^0.1.0-beta-2:
|
||||
version "0.1.0-beta-2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-ligatures/-/xterm-addon-ligatures-0.1.0-beta-2.tgz#def635fd0ca671fe61179629f8492b76c66dec6e"
|
||||
@ -254,9 +259,14 @@ xterm-addon-ligatures@^0.1.0-beta-2:
|
||||
font-finder "^1.0.2"
|
||||
font-ligatures "^1.3.1"
|
||||
|
||||
"xterm@https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.1.tgz":
|
||||
version "3.14.1"
|
||||
resolved "https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.1.tgz#f3f7c0b4726fc60c22af17bac95c1b8058eca317"
|
||||
xterm-addon-search@^0.1.0-beta6:
|
||||
version "0.1.0-beta6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta6.tgz#e2a2b441f8f7b0245c63731d0b2af32c7d4e6747"
|
||||
integrity sha512-XKxdfO48HkCJW2m1wXW0PK/BOk00WEaN+W2LgDQqCBwwUjyBzWc9HaV8gzLXhSCDAYesWvtQa3RfqHfSp9qsbQ==
|
||||
|
||||
"xterm@https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.2.tgz":
|
||||
version "3.14.2"
|
||||
resolved "https://registry.npmjs.org/@terminus-term/xterm/-/xterm-3.14.2.tgz#3ad13ca05ef024e8fb993d5c8320ca56a43eaf8e"
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
|
Loading…
x
Reference in New Issue
Block a user