From 565cba9c1ae6393c482b64e9a7a99bfa06e7776e Mon Sep 17 00:00:00 2001 From: ntchjb Date: Sat, 29 Dec 2018 09:38:17 +0700 Subject: [PATCH 01/83] Fix search addons: - Changed to use `this._terminal.cols` for buffer line length instead. - Let `_findInLine` check on wrapped line - Added conditions in `_findInLine` - For reverse search, if there is no selection at given row, then start scan at the end of the string --- src/addons/search/SearchHelper.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 96cd845d4..3537295d1 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -101,7 +101,7 @@ export class SearchHelper implements ISearchHelper { const isReverseSearch = true; let startRow = this._terminal._core.buffer.ydisp; - let startCol: number = this._terminal._core.buffer.lines.get(startRow).length; + let startCol: number = this._terminal.cols; if (selectionManager.selectionStart) { // Start from the selection start if there is a selection @@ -119,7 +119,7 @@ export class SearchHelper implements ISearchHelper { // Search from startRow - 1 to top if (!result) { for (let y = startRow - 1; y >= 0; y--) { - result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch); + result = this._findInLine(term, y, this._terminal.cols, searchOptions, isReverseSearch); if (result) { break; } @@ -131,7 +131,7 @@ export class SearchHelper implements ISearchHelper { if (!result) { const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1; for (let y = searchFrom; y >= startRow; y--) { - result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch); + result = this._findInLine(term, y, this._terminal.cols, searchOptions, isReverseSearch); if (result) { break; } @@ -187,9 +187,6 @@ export class SearchHelper implements ISearchHelper { * @return The search result if it was found. */ protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult { - if (this._terminal._core.buffer.lines.get(row).isWrapped) { - return; - } let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { @@ -222,7 +219,12 @@ export class SearchHelper implements ISearchHelper { } } else { if (isReverseSearch) { - if (col - searchTerm.length >= 0) { + // If the given row has no selection (col is equal to row length), + // lastIndexOf needs to scan at the end of the searchStringLine + if (col === this._terminal.cols) { + resultIndex = searchStringLine.lastIndexOf(searchTerm, col - 1); + } + else if (col - searchTerm.length >= 0) { resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length); } } else { From d94c89775421e5c98ccf8552c5ef373e9ca7ceb3 Mon Sep 17 00:00:00 2001 From: ntchjb Date: Sat, 29 Dec 2018 13:55:14 +0700 Subject: [PATCH 02/83] Fix search addons: be able to search wrapped lines from unwrapped line by managing column range that is needed to be scanned. --- src/addons/search/SearchHelper.ts | 70 ++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 3537295d1..7200539be 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -60,22 +60,54 @@ export class SearchHelper implements ISearchHelper { // Search from startRow + 1 to end if (!result) { + // 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. + while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + findingRow--; + cumulativeCols += this._terminal.cols; + } + for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { - result = this._findInLine(term, y, 0, searchOptions); + // Run _findInLine at unwrapped row, scan for cumulativeCols columns + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); if (result) { break; } + // 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 + if (this._terminal._core.buffer.lines.get(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } else { + cumulativeCols = 0; + findingRow = y; + } } } // Search from the top to the startRow (search the whole startRow again in // case startCol > 0) if (!result) { + // Assume that The first line is always unwrapped line + let findingRow = 0; + // Scan at beginning of the line + let cumulativeCols = 0; for (let y = 0; y <= startRow; y++) { - result = this._findInLine(term, y, 0, searchOptions); + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); if (result) { break; } + // If the current line is wrapped line, increase index of beginning column + // So we ignore the previous scan + if (this._terminal._core.buffer.lines.get(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } else { + cumulativeCols = 0; + findingRow = y; + } } } @@ -118,11 +150,24 @@ export class SearchHelper implements ISearchHelper { // 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._core.buffer.lines.get(startRow).isWrapped) { + cumulativeCols += startCol; + } for (let y = startRow - 1; y >= 0; y--) { - result = this._findInLine(term, y, this._terminal.cols, searchOptions, isReverseSearch); + 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 + if (this._terminal._core.buffer.lines.get(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } else { + cumulativeCols = this._terminal.cols; + } } } @@ -130,11 +175,17 @@ export class SearchHelper implements ISearchHelper { // case startCol > 0) if (!result) { const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1; + let cumulativeCols = this._terminal.cols; for (let y = searchFrom; y >= startRow; y--) { - result = this._findInLine(term, y, this._terminal.cols, searchOptions, isReverseSearch); + result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); if (result) { break; } + if (this._terminal._core.buffer.lines.get(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } else { + cumulativeCols = this._terminal.cols; + } } } @@ -188,6 +239,10 @@ export class SearchHelper implements ISearchHelper { */ protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult { + // Ignore wrapped lines, only consider on unwrapped line (first row of command string). + if (this._terminal._core.buffer.lines.get(row).isWrapped) { + return; + } let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { stringLine = this.translateBufferLineToStringWithWrap(row, true); @@ -219,12 +274,7 @@ export class SearchHelper implements ISearchHelper { } } else { if (isReverseSearch) { - // If the given row has no selection (col is equal to row length), - // lastIndexOf needs to scan at the end of the searchStringLine - if (col === this._terminal.cols) { - resultIndex = searchStringLine.lastIndexOf(searchTerm, col - 1); - } - else if (col - searchTerm.length >= 0) { + if (col - searchTerm.length >= 0) { resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length); } } else { From f961f90bc83ab249529f4187ecb250168176bfbf Mon Sep 17 00:00:00 2001 From: ntchjb Date: Tue, 1 Jan 2019 16:39:23 +0700 Subject: [PATCH 03/83] Support wide characters and combined characters - Convert buffer index to string index before searching - Convert string index to buffer index after searching - Fix bug when search selection skipped some result --- src/addons/search/SearchHelper.ts | 214 +++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 50 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7200539be..69fd919f3 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -7,6 +7,8 @@ import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } fr const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs +const CHAR_DATA_CHAR_INDEX = 1; +const CHAR_DATA_WIDTH_INDEX = 2; /** * A class that knows how to search the terminal and how to display the results. @@ -55,28 +57,25 @@ export class SearchHelper implements ISearchHelper { this._initLinesCache(); - // Search startRow - result = this._findInLine(term, startRow, startCol, searchOptions); + // The 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. + while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + findingRow--; + cumulativeCols += this._terminal.cols; + } - // Search from startRow + 1 to end + // Search unwarpped row + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + + // Search from startRow + 1 to end, if the row is still wrapped line, increase cumulativeCols, + // otherwise, reset it and set the new unwrapped line index. if (!result) { - // 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. - while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { - findingRow--; - cumulativeCols += this._terminal.cols; - } for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { - // Run _findInLine at unwrapped row, scan for cumulativeCols columns - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); - if (result) { - break; - } // 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 if (this._terminal._core.buffer.lines.get(y).isWrapped) { @@ -85,6 +84,11 @@ export class SearchHelper implements ISearchHelper { cumulativeCols = 0; findingRow = y; } + // Run _findInLine at unwrapped row, start scan at cumulativeCols column index + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + if (result) { + break; + } } } @@ -226,11 +230,129 @@ export class SearchHelper implements ISearchHelper { (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1))); } + /** + * Translates a string index back to a BufferIndex. + * To get the correct buffer position the string must start at `startCol` 0 + * (default in translateBufferLineToString). + * This method is similar to stringIndexToBufferIndex in Buffer.ts + * but this method added some modification that, if it found an empty cell, + * the method will see it as a whitespace and count it as a character. + * The modification is added because the given string index may include + * empty cells inside the string, which is needed to be counted. + * The return value of this method is the same as BufferIndex + * @param lineIndex line index the string was retrieved from + * @param stringIndex index within the string + * @param startCol column offset the string was retrieved from + */ + private _stringIndexToBufferIndex(lineIndex: number, stringIndex: number): [number, number] { + while (stringIndex) { + const line = this._terminal._core.buffer.lines.get(lineIndex); + if (!line) { + return [-1, -1]; + } + for (let i = 0; i < this._terminal.cols; ++i) { + const charData = line.get(i); + const char = charData[CHAR_DATA_CHAR_INDEX]; + // If found empty cell with width equals to 1, see it as whitespace + if (charData[CHAR_DATA_CHAR_INDEX] === '' && charData[CHAR_DATA_WIDTH_INDEX] > 0) { + stringIndex--; + } + stringIndex -= char.length; + if (stringIndex < 0) { + return [lineIndex, i]; + } + } + lineIndex++; + } + return [lineIndex, 0]; + } + + /** + * Convert buffer index of unwrapped row to string index. + * @param lineIndex index of terminal row that is unwrapped + * @param bufferIndex index of terminal column on unwrapped row + */ + private _bufferIndexToStringIndex(lineIndex: number, bufferIndex: number): number { + let stringIndex = -1; + const buffer = this._terminal._core.buffer; + while (bufferIndex >= 0) { + const line = buffer.lines.get(lineIndex); + // Exceed index of bottom row, returned + if (!line) { + break; + } + + let lineLength = this._terminal.cols; + + // At the last line, lineLength will be trimmed to remove trailing empty cells + if (bufferIndex < lineLength) { + // Add 1 to getTrimmedLength because if providing bufferIndex is larger than + // converted string length, the result should be `string length`, not `string length - 1` + // to make sure that searching range includes the last character in the string + lineLength = line.getTrimmedLength() + 1; + } + + for (let i = 0; i < lineLength; i++) { + const cell = line.get(i); + + // Count number of characters from current buffer column in each cell. + stringIndex += cell[CHAR_DATA_CHAR_INDEX].length; + bufferIndex--; + + // If found empty cell, act like found whitespace + if (cell[CHAR_DATA_CHAR_INDEX] === '' && cell[CHAR_DATA_WIDTH_INDEX] > 0) { + stringIndex++; + } + + if (bufferIndex < 0) { + return stringIndex; + } + } + lineIndex++; + } + return stringIndex; + } + + /** + * Get buffer length (number of cells) from provided string + * @param result The search result object including term, row, and col + */ + private _getCellLengthFromString(result: ISearchResult): number { + const length = result.term.length; + + let strCount = 0; + let cellCount = 0; + let { col, row } = result; + let rowContent = this._terminal._core.buffer.lines.get(row); + + // Count cells along with characters until the number of characters + // exceeds string length of search result. + while (strCount <= length) { + strCount += rowContent.get(col)[CHAR_DATA_CHAR_INDEX].length; + cellCount += rowContent.get(col)[CHAR_DATA_WIDTH_INDEX]; + if (strCount >= length) { + break; + } + col++; + + // In case that current cell exceed total number of cells in a row + // Begin col at 0 on the next line + if (col >= this._terminal.cols) { + col = 0; + rowContent = this._terminal._core.buffer.lines.get(++row); + } + } + return cellCount; + } /** * Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. + * + * The concept of searching is that: + * Get unwarpped line as string => convert rowand col to string index => begin searching to get search result as + * string index => convert back to buffer index (col, row) => return the result. * @param term The search term. * @param row The line to start the search from. * @param col The column to start the search from. @@ -243,6 +365,8 @@ export class SearchHelper implements ISearchHelper { if (this._terminal._core.buffer.lines.get(row).isWrapped) { return; } + + // Get unwrapped string from buffer lines let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { stringLine = this.translateBufferLineToStringWithWrap(row, true); @@ -251,67 +375,54 @@ export class SearchHelper implements ISearchHelper { } } + // Check for case sensitive option const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase(); const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase(); let resultIndex = -1; + // Convert from buffer index (col, row) to string index before begin searching + const stringIndex = this._bufferIndexToStringIndex(row, col); + if (searchOptions.regex) { const searchRegex = RegExp(searchTerm, 'g'); let foundTerm: RegExpExecArray; 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))) { + while (foundTerm = searchRegex.exec(searchStringLine.slice(0, stringIndex))) { resultIndex = searchRegex.lastIndex - foundTerm[0].length; term = foundTerm[0]; searchRegex.lastIndex -= (term.length - 1); } } else { - foundTerm = searchRegex.exec(searchStringLine.slice(col)); + foundTerm = searchRegex.exec(searchStringLine.slice(stringIndex)); if (foundTerm && foundTerm[0].length > 0) { - resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length); + resultIndex = stringIndex + (searchRegex.lastIndex - foundTerm[0].length); term = foundTerm[0]; } } } else { if (isReverseSearch) { - if (col - searchTerm.length >= 0) { - resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length); + if (stringIndex - searchTerm.length >= 0) { + resultIndex = searchStringLine.lastIndexOf(searchTerm, stringIndex - searchTerm.length); } } else { - resultIndex = searchStringLine.indexOf(searchTerm, col); + resultIndex = searchStringLine.indexOf(searchTerm, stringIndex); } } if (resultIndex >= 0) { - // Adjust the row number and search index if needed since a "line" of text can span multiple rows - if (resultIndex >= this._terminal.cols) { - row += Math.floor(resultIndex / this._terminal.cols); - resultIndex = resultIndex % this._terminal.cols; - } + // After getting the result as string index, convert it to buffer index. + const resultBufferIndex = this._stringIndexToBufferIndex(row, resultIndex); + + // Check for wholeword option if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) { return; } - const line = this._terminal._core.buffer.lines.get(row); - - for (let i = 0; i < resultIndex; i++) { - const charData = line.get(i); - // Adjust the searchIndex to normalize emoji into single chars - const char = charData[1/*CHAR_DATA_CHAR_INDEX*/]; - if (char.length > 1) { - resultIndex -= char.length - 1; - } - // Adjust the searchIndex for empty characters following wide unicode - // chars (eg. CJK) - const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/]; - if (charWidth === 0) { - resultIndex++; - } - } return { term, - col: resultIndex, - row + col: resultBufferIndex[1], + row: resultBufferIndex[0] }; } } @@ -321,7 +432,7 @@ export class SearchHelper implements ISearchHelper { * function is useful for getting the actual text underneath the raw selection * position. * @param line The line being translated. - * @param trimRight Whether to trim whitespace to the right. + * @param trimRight Whether to trim -space to the right. */ public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string { let lineString = ''; @@ -330,7 +441,8 @@ export class SearchHelper implements ISearchHelper { do { const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols); + // string should be cut with string index, not buffer index to support wide characters + lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._bufferIndexToStringIndex(lineIndex, this._terminal.cols)); lineIndex++; } while (lineWrapsToNext); @@ -347,7 +459,9 @@ export class SearchHelper implements ISearchHelper { this._terminal.clearSelection(); return false; } - this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); + // The selection length should be number of cell needed to be selected, not string length. + // To support wide character + this._terminal._core.selectionManager.setSelection(result.col, result.row, this._getCellLengthFromString(result)); this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); return true; } From ff81ce078ba997640bab069595e5ad7dccbd2623 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 09:38:40 -0800 Subject: [PATCH 04/83] Add a release pipelines job --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a26bd3d84..19c8044b8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -66,3 +66,22 @@ jobs: - script: | yarn lint displayName: 'Lint' + +- job: Release + dependsOn: + - Linux + - macOS + - Windows + pool: + vmImage: 'xcode9-macos10.13' + steps: + - task: NodeTool@0 + inputs: + versionSpec: '8.x' + displayName: 'Install Node.js' + - script: | + yarn + displayName: 'Install dependencies and build' + - script: | + echo 'success' + displayName: 'Test' From a6c3f948604e1814a854221d4a9a424057907567 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 11:24:06 -0800 Subject: [PATCH 05/83] Publish to npm as beta --- .npmignore | 1 + azure-pipelines.yml | 13 +++++++++--- bin/publish.js | 48 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 bin/publish.js diff --git a/.npmignore b/.npmignore index 517ba0bdc..03030ba59 100644 --- a/.npmignore +++ b/.npmignore @@ -42,6 +42,7 @@ lib/test/ docs/ /.idea/ .vscode/ +bin/ build/ fixtures/ coverage/ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 19c8044b8..a701b9a5b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -72,16 +72,23 @@ jobs: - Linux - macOS - Windows + #condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) pool: - vmImage: 'xcode9-macos10.13' + vmImage: 'ubuntu-16.04' steps: - task: NodeTool@0 inputs: versionSpec: '8.x' displayName: 'Install Node.js' + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.9.4" + displayName: 'Install Yarn' - script: | yarn displayName: 'Install dependencies and build' - script: | - echo 'success' - displayName: 'Test' + node ./bin/publish.js + env: + NPM_AUTH_TOKEN: $(NPM_AUTH_TOKEN) + displayName: 'Publish to npm' diff --git a/bin/publish.js b/bin/publish.js new file mode 100644 index 000000000..e78711553 --- /dev/null +++ b/bin/publish.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const packageJson = require('../package.json'); + +// Setup auth +fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`); + +// Get the version +const tag = 'beta' +const nextVersion = getNextVersion(tag); +console.log(`Publishing version: ${nextVersion}`); + +// Set the version in package.json +const packageJsonFile = path.resolve(__dirname, '..', 'package.json'); +let packageJsonRaw = fs.readFileSync(packageJsonFile).toString(); +packageJsonRaw = packageJsonRaw.replace(/("version": ")3.10.0(")/, `$1${nextVersion}$2`); +fs.writeFileSync(packageJsonFile, packageJsonRaw); + +// Publish +cp.spawnSync('npm', ['publish', '--tag', tag], { + stdio: 'inherit' +}); + +function getNextVersion(tag) { + if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) { + console.error('The package.json version must be of the form x.y.z'); + process.exit(1); + } + const publishedVersions = getPublishedVersions(packageJson.version, tag); + if (publishedVersions.length === 0) { + return `${packageJson.version}-${tag}1`; + } + const latestPublishedVersion = publishedVersions.sort((a, b) => b.localeCompare(a))[0]; + const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); + return `${packageJson.version}-${tag}${latestTagVersion + 1}`; +} + +function getPublishedVersions(version, tag) { + const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', 'versions', '--json']); + const versionsJson = JSON.parse(versionsProcess.stdout); + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); +} diff --git a/package.json b/package.json index 0d797f789..5ed3a4b80 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.9.0", + "version": "3.10.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", From aa2d28e619457d30a375dbe0748bf73e6adfc35f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 11:37:34 -0800 Subject: [PATCH 06/83] Disable dependsOn --- azure-pipelines.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a701b9a5b..2126a26d3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,10 +68,10 @@ jobs: displayName: 'Lint' - job: Release - dependsOn: - - Linux - - macOS - - Windows + # dependsOn: + # - Linux + # - macOS + # - Windows #condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) pool: vmImage: 'ubuntu-16.04' @@ -88,6 +88,7 @@ jobs: yarn displayName: 'Install dependencies and build' - script: | + echo $NPM_AUTH_TOKEN node ./bin/publish.js env: NPM_AUTH_TOKEN: $(NPM_AUTH_TOKEN) From a35ad6aa810003240312c2f0c87bde719b244ec0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 11:39:44 -0800 Subject: [PATCH 07/83] Inherit exit code from publish call --- bin/publish.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index e78711553..d5bebe027 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -23,9 +23,10 @@ packageJsonRaw = packageJsonRaw.replace(/("version": ")3.10.0(")/, `$1${nextVers fs.writeFileSync(packageJsonFile, packageJsonRaw); // Publish -cp.spawnSync('npm', ['publish', '--tag', tag], { +const result = cp.spawn('npm', ['publish', '--tag', tag], { stdio: 'inherit' }); +result.on('exit', code => process.exit(code)); function getNextVersion(tag) { if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) { From 515cf7878ef15d6f77f864fff38d7e13d61881fc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 11:49:20 -0800 Subject: [PATCH 08/83] Tweak var --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2126a26d3..d3568a2fe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,8 +88,8 @@ jobs: yarn displayName: 'Install dependencies and build' - script: | - echo $NPM_AUTH_TOKEN + echo $MAPPED_NPM_AUTH_TOKEN node ./bin/publish.js env: - NPM_AUTH_TOKEN: $(NPM_AUTH_TOKEN) + MAPPED_NPM_AUTH_TOKEN: $(NPM_AUTH_TOKEN) displayName: 'Publish to npm' From 68fbb2f8c98eb5aa69e9ab0b8ec102c11fff6358 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 11:53:51 -0800 Subject: [PATCH 09/83] Tweak more --- azure-pipelines.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d3568a2fe..11ceefbba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,8 +88,6 @@ jobs: yarn displayName: 'Install dependencies and build' - script: | - echo $MAPPED_NPM_AUTH_TOKEN - node ./bin/publish.js - env: - MAPPED_NPM_AUTH_TOKEN: $(NPM_AUTH_TOKEN) + echo $(NPM_AUTH_TOKEN) + NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js displayName: 'Publish to npm' From d120ef5301b644148dd6537592f226566672c9b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 12:55:44 -0800 Subject: [PATCH 10/83] Re-enable condition and dependsOn --- azure-pipelines.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 11ceefbba..6101c802d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,11 +68,11 @@ jobs: displayName: 'Lint' - job: Release - # dependsOn: - # - Linux - # - macOS - # - Windows - #condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + dependsOn: + - Linux + - macOS + - Windows + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) pool: vmImage: 'ubuntu-16.04' steps: @@ -88,6 +88,5 @@ jobs: yarn displayName: 'Install dependencies and build' - script: | - echo $(NPM_AUTH_TOKEN) NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js displayName: 'Publish to npm' From 4db95f253ef7959257047e5aca95858db3b258f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 13:40:06 -0800 Subject: [PATCH 11/83] Build to dist dir in release build --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6101c802d..db74a4ea6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -86,6 +86,7 @@ jobs: displayName: 'Install Yarn' - script: | yarn + BUILD_DIR=dist npm run build displayName: 'Install dependencies and build' - script: | NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js From efe22d8cf3e527fc55090bff175158b938b9eeb3 Mon Sep 17 00:00:00 2001 From: ntchjb Date: Thu, 3 Jan 2019 15:32:25 +0700 Subject: [PATCH 12/83] Revert "Support wide characters and combined characters" This reverts commit f961f90bc83ab249529f4187ecb250168176bfbf. --- src/addons/search/SearchHelper.ts | 214 +++++++----------------------- 1 file changed, 50 insertions(+), 164 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 69fd919f3..7200539be 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -7,8 +7,6 @@ import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } fr const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs -const CHAR_DATA_CHAR_INDEX = 1; -const CHAR_DATA_WIDTH_INDEX = 2; /** * A class that knows how to search the terminal and how to display the results. @@ -57,25 +55,28 @@ export class SearchHelper implements ISearchHelper { this._initLinesCache(); - // The 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. - while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { - findingRow--; - cumulativeCols += this._terminal.cols; - } + // Search startRow + result = this._findInLine(term, startRow, startCol, searchOptions); - // Search unwarpped row - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); - - // Search from startRow + 1 to end, if the row is still wrapped line, increase cumulativeCols, - // otherwise, reset it and set the new unwrapped line index. + // Search from startRow + 1 to end if (!result) { + // 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. + while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + findingRow--; + cumulativeCols += this._terminal.cols; + } for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { + // Run _findInLine at unwrapped row, scan for cumulativeCols columns + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + if (result) { + break; + } // 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 if (this._terminal._core.buffer.lines.get(y).isWrapped) { @@ -84,11 +85,6 @@ export class SearchHelper implements ISearchHelper { cumulativeCols = 0; findingRow = y; } - // Run _findInLine at unwrapped row, start scan at cumulativeCols column index - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); - if (result) { - break; - } } } @@ -230,129 +226,11 @@ export class SearchHelper implements ISearchHelper { (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1))); } - /** - * Translates a string index back to a BufferIndex. - * To get the correct buffer position the string must start at `startCol` 0 - * (default in translateBufferLineToString). - * This method is similar to stringIndexToBufferIndex in Buffer.ts - * but this method added some modification that, if it found an empty cell, - * the method will see it as a whitespace and count it as a character. - * The modification is added because the given string index may include - * empty cells inside the string, which is needed to be counted. - * The return value of this method is the same as BufferIndex - * @param lineIndex line index the string was retrieved from - * @param stringIndex index within the string - * @param startCol column offset the string was retrieved from - */ - private _stringIndexToBufferIndex(lineIndex: number, stringIndex: number): [number, number] { - while (stringIndex) { - const line = this._terminal._core.buffer.lines.get(lineIndex); - if (!line) { - return [-1, -1]; - } - for (let i = 0; i < this._terminal.cols; ++i) { - const charData = line.get(i); - const char = charData[CHAR_DATA_CHAR_INDEX]; - // If found empty cell with width equals to 1, see it as whitespace - if (charData[CHAR_DATA_CHAR_INDEX] === '' && charData[CHAR_DATA_WIDTH_INDEX] > 0) { - stringIndex--; - } - stringIndex -= char.length; - if (stringIndex < 0) { - return [lineIndex, i]; - } - } - lineIndex++; - } - return [lineIndex, 0]; - } - - /** - * Convert buffer index of unwrapped row to string index. - * @param lineIndex index of terminal row that is unwrapped - * @param bufferIndex index of terminal column on unwrapped row - */ - private _bufferIndexToStringIndex(lineIndex: number, bufferIndex: number): number { - let stringIndex = -1; - const buffer = this._terminal._core.buffer; - while (bufferIndex >= 0) { - const line = buffer.lines.get(lineIndex); - // Exceed index of bottom row, returned - if (!line) { - break; - } - - let lineLength = this._terminal.cols; - - // At the last line, lineLength will be trimmed to remove trailing empty cells - if (bufferIndex < lineLength) { - // Add 1 to getTrimmedLength because if providing bufferIndex is larger than - // converted string length, the result should be `string length`, not `string length - 1` - // to make sure that searching range includes the last character in the string - lineLength = line.getTrimmedLength() + 1; - } - - for (let i = 0; i < lineLength; i++) { - const cell = line.get(i); - - // Count number of characters from current buffer column in each cell. - stringIndex += cell[CHAR_DATA_CHAR_INDEX].length; - bufferIndex--; - - // If found empty cell, act like found whitespace - if (cell[CHAR_DATA_CHAR_INDEX] === '' && cell[CHAR_DATA_WIDTH_INDEX] > 0) { - stringIndex++; - } - - if (bufferIndex < 0) { - return stringIndex; - } - } - lineIndex++; - } - return stringIndex; - } - - /** - * Get buffer length (number of cells) from provided string - * @param result The search result object including term, row, and col - */ - private _getCellLengthFromString(result: ISearchResult): number { - const length = result.term.length; - - let strCount = 0; - let cellCount = 0; - let { col, row } = result; - let rowContent = this._terminal._core.buffer.lines.get(row); - - // Count cells along with characters until the number of characters - // exceeds string length of search result. - while (strCount <= length) { - strCount += rowContent.get(col)[CHAR_DATA_CHAR_INDEX].length; - cellCount += rowContent.get(col)[CHAR_DATA_WIDTH_INDEX]; - if (strCount >= length) { - break; - } - col++; - - // In case that current cell exceed total number of cells in a row - // Begin col at 0 on the next line - if (col >= this._terminal.cols) { - col = 0; - rowContent = this._terminal._core.buffer.lines.get(++row); - } - } - return cellCount; - } /** * Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. - * - * The concept of searching is that: - * Get unwarpped line as string => convert rowand col to string index => begin searching to get search result as - * string index => convert back to buffer index (col, row) => return the result. * @param term The search term. * @param row The line to start the search from. * @param col The column to start the search from. @@ -365,8 +243,6 @@ export class SearchHelper implements ISearchHelper { if (this._terminal._core.buffer.lines.get(row).isWrapped) { return; } - - // Get unwrapped string from buffer lines let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { stringLine = this.translateBufferLineToStringWithWrap(row, true); @@ -375,54 +251,67 @@ export class SearchHelper implements ISearchHelper { } } - // Check for case sensitive option const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase(); const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase(); let resultIndex = -1; - // Convert from buffer index (col, row) to string index before begin searching - const stringIndex = this._bufferIndexToStringIndex(row, col); - if (searchOptions.regex) { const searchRegex = RegExp(searchTerm, 'g'); let foundTerm: RegExpExecArray; 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, stringIndex))) { + 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(stringIndex)); + foundTerm = searchRegex.exec(searchStringLine.slice(col)); if (foundTerm && foundTerm[0].length > 0) { - resultIndex = stringIndex + (searchRegex.lastIndex - foundTerm[0].length); + resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length); term = foundTerm[0]; } } } else { if (isReverseSearch) { - if (stringIndex - searchTerm.length >= 0) { - resultIndex = searchStringLine.lastIndexOf(searchTerm, stringIndex - searchTerm.length); + if (col - searchTerm.length >= 0) { + resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length); } } else { - resultIndex = searchStringLine.indexOf(searchTerm, stringIndex); + resultIndex = searchStringLine.indexOf(searchTerm, col); } } if (resultIndex >= 0) { - // After getting the result as string index, convert it to buffer index. - const resultBufferIndex = this._stringIndexToBufferIndex(row, resultIndex); - - // Check for wholeword option + // Adjust the row number and search index if needed since a "line" of text can span multiple rows + if (resultIndex >= this._terminal.cols) { + row += Math.floor(resultIndex / this._terminal.cols); + resultIndex = resultIndex % this._terminal.cols; + } if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) { return; } + const line = this._terminal._core.buffer.lines.get(row); + + for (let i = 0; i < resultIndex; i++) { + const charData = line.get(i); + // Adjust the searchIndex to normalize emoji into single chars + const char = charData[1/*CHAR_DATA_CHAR_INDEX*/]; + if (char.length > 1) { + resultIndex -= char.length - 1; + } + // Adjust the searchIndex for empty characters following wide unicode + // chars (eg. CJK) + const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/]; + if (charWidth === 0) { + resultIndex++; + } + } return { term, - col: resultBufferIndex[1], - row: resultBufferIndex[0] + col: resultIndex, + row }; } } @@ -432,7 +321,7 @@ export class SearchHelper implements ISearchHelper { * function is useful for getting the actual text underneath the raw selection * position. * @param line The line being translated. - * @param trimRight Whether to trim -space to the right. + * @param trimRight Whether to trim whitespace to the right. */ public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string { let lineString = ''; @@ -441,8 +330,7 @@ export class SearchHelper implements ISearchHelper { do { const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - // string should be cut with string index, not buffer index to support wide characters - lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._bufferIndexToStringIndex(lineIndex, this._terminal.cols)); + lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols); lineIndex++; } while (lineWrapsToNext); @@ -459,9 +347,7 @@ export class SearchHelper implements ISearchHelper { this._terminal.clearSelection(); return false; } - // The selection length should be number of cell needed to be selected, not string length. - // To support wide character - this._terminal._core.selectionManager.setSelection(result.col, result.row, this._getCellLengthFromString(result)); + this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); return true; } From c784a012c79783084b9fa4b604f676a5f1cfd3dc Mon Sep 17 00:00:00 2001 From: ntchjb Date: Thu, 3 Jan 2019 15:55:27 +0700 Subject: [PATCH 13/83] Fix: update findingRow and cumulativeCols before running _findInLine to be able to scan unwrapped line at the last row --- src/addons/search/SearchHelper.ts | 35 +++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7200539be..0b7e89603 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -55,28 +55,25 @@ export class SearchHelper implements ISearchHelper { 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. + while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + findingRow--; + cumulativeCols += this._terminal.cols; + } + // Search startRow - result = this._findInLine(term, startRow, startCol, searchOptions); + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); // Search from startRow + 1 to end if (!result) { - // 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. - while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { - findingRow--; - cumulativeCols += this._terminal.cols; - } for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { - // Run _findInLine at unwrapped row, scan for cumulativeCols columns - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); - if (result) { - break; - } + // 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 if (this._terminal._core.buffer.lines.get(y).isWrapped) { @@ -85,6 +82,12 @@ export class SearchHelper implements ISearchHelper { cumulativeCols = 0; findingRow = y; } + + // Run _findInLine at unwrapped row, scan for cumulativeCols columns + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + if (result) { + break; + } } } From 92369345867d2e6f79722fcf5bac82f5a4e51d2d Mon Sep 17 00:00:00 2001 From: ntchjb Date: Wed, 9 Jan 2019 07:01:34 +0700 Subject: [PATCH 14/83] _findInLine() function should not run multiple times on the same row --- src/addons/search/SearchHelper.ts | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 0b7e89603..525e25dc7 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -76,15 +76,7 @@ export class SearchHelper implements ISearchHelper { // 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 - if (this._terminal._core.buffer.lines.get(y).isWrapped) { - cumulativeCols += this._terminal.cols; - } else { - cumulativeCols = 0; - findingRow = y; - } - - // Run _findInLine at unwrapped row, scan for cumulativeCols columns - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + result = this._findInLine(term, y, 0, searchOptions); if (result) { break; } @@ -94,23 +86,11 @@ export class SearchHelper implements ISearchHelper { // Search from the top to the startRow (search the whole startRow again in // case startCol > 0) if (!result) { - // Assume that The first line is always unwrapped line - let findingRow = 0; - // Scan at beginning of the line - let cumulativeCols = 0; - for (let y = 0; y <= startRow; y++) { - result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + for (let y = 0; y < findingRow; y++) { + result = this._findInLine(term, y, 0, searchOptions); if (result) { break; } - // If the current line is wrapped line, increase index of beginning column - // So we ignore the previous scan - if (this._terminal._core.buffer.lines.get(y).isWrapped) { - cumulativeCols += this._terminal.cols; - } else { - cumulativeCols = 0; - findingRow = y; - } } } From b0470459493771db901d58489e419f9c5349033b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 27 Jan 2019 07:28:04 -0800 Subject: [PATCH 15/83] Replace hardcoded version --- bin/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index d5bebe027..6ffb1c25d 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -19,7 +19,7 @@ console.log(`Publishing version: ${nextVersion}`); // Set the version in package.json const packageJsonFile = path.resolve(__dirname, '..', 'package.json'); let packageJsonRaw = fs.readFileSync(packageJsonFile).toString(); -packageJsonRaw = packageJsonRaw.replace(/("version": ")3.10.0(")/, `$1${nextVersion}$2`); +packageJsonRaw = packageJsonRaw.replace(/("version": ")[0-9]+\.[0-9]+\.[0-9]+(")/, `$1${nextVersion}$2`); fs.writeFileSync(packageJsonFile, packageJsonRaw); // Publish From af6544d6d92c14d95ea3529a1f0126cbca9ba60f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 28 Jan 2019 07:23:46 -0800 Subject: [PATCH 16/83] Improve package.json modifications --- bin/publish.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 6ffb1c25d..700752b48 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -18,9 +18,8 @@ console.log(`Publishing version: ${nextVersion}`); // Set the version in package.json const packageJsonFile = path.resolve(__dirname, '..', 'package.json'); -let packageJsonRaw = fs.readFileSync(packageJsonFile).toString(); -packageJsonRaw = packageJsonRaw.replace(/("version": ")[0-9]+\.[0-9]+\.[0-9]+(")/, `$1${nextVersion}$2`); -fs.writeFileSync(packageJsonFile, packageJsonRaw); +packageJson.version = nextVersion; +fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); // Publish const result = cp.spawn('npm', ['publish', '--tag', tag], { From 9c72254d3a77f7dc62c15cc475b2080a74760101 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 28 Jan 2019 07:23:58 -0800 Subject: [PATCH 17/83] Bump to 3.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ed3a4b80..fa9cc0706 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.10.0", + "version": "3.11.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", From e813ae02fc5a140c3c5d481f2c7ca184de887297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 1 Feb 2019 20:28:45 +0100 Subject: [PATCH 18/83] return true from inputhandler methods to signal sequence was handled --- src/InputHandler.ts | 183 +++++++++++++++++++++++++++++--------------- src/Types.ts | 108 +++++++++++++------------- 2 files changed, 176 insertions(+), 115 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2f53cfcbb..4af525f2a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -446,15 +446,16 @@ export class InputHandler extends Disposable implements IInputHandler { * BEL * Bell (Ctrl-G). */ - public bell(): void { + public bell(): boolean { this._terminal.bell(); + return true; } /** * LF * Line Feed or New Line (NL). (LF is Ctrl-J). */ - public lineFeed(): void { + public lineFeed(): boolean { // make buffer local for faster access const buffer = this._terminal.buffer; @@ -476,36 +477,40 @@ export class InputHandler extends Disposable implements IInputHandler { * @event linefeed */ this._terminal.emit('linefeed'); + return true; } /** * CR * Carriage Return (Ctrl-M). */ - public carriageReturn(): void { + public carriageReturn(): boolean { this._terminal.buffer.x = 0; + return true; } /** * BS * Backspace (Ctrl-H). */ - public backspace(): void { + public backspace(): boolean { if (this._terminal.buffer.x > 0) { this._terminal.buffer.x--; } + return true; } /** * TAB * Horizontal Tab (HT) (Ctrl-I). */ - public tab(): void { + public tab(): boolean { const originalX = this._terminal.buffer.x; this._terminal.buffer.x = this._terminal.buffer.nextStop(); if (this._terminal.options.screenReaderMode) { this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); } + return true; } /** @@ -513,8 +518,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. */ - public shiftOut(): void { + public shiftOut(): boolean { this._terminal.setgLevel(1); + return true; } /** @@ -522,28 +528,30 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). */ - public shiftIn(): void { + public shiftIn(): boolean { this._terminal.setgLevel(0); + return true; } /** * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ - public insertChars(params: number[]): void { + public insertChars(params: number[]): boolean { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] ); this._terminal.updateRange(this._terminal.buffer.y); + return true; } /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ - public cursorUp(params: number[]): void { + public cursorUp(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -552,13 +560,14 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.y < 0) { this._terminal.buffer.y = 0; } + return true; } /** * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[]): void { + public cursorDown(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -571,13 +580,14 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x--; } + return true; } /** * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[]): void { + public cursorForward(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -586,13 +596,14 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } + return true; } /** * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[]): void { + public cursorBackward(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -605,6 +616,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x < 0) { this._terminal.buffer.x = 0; } + return true; } /** @@ -612,7 +624,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * same as CSI Ps B ? */ - public cursorNextLine(params: number[]): void { + public cursorNextLine(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -622,6 +634,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = this._terminal.rows - 1; } this._terminal.buffer.x = 0; + return true; } @@ -630,7 +643,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Preceding Line Ps Times (default = 1) (CNL). * reuse CSI Ps A ? */ - public cursorPrecedingLine(params: number[]): void { + public cursorPrecedingLine(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -640,6 +653,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = 0; } this._terminal.buffer.x = 0; + return true; } @@ -647,19 +661,20 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). */ - public cursorCharAbsolute(params: number[]): void { + public cursorCharAbsolute(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; } this._terminal.buffer.x = param - 1; + return true; } /** * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ - public cursorPosition(params: number[]): void { + public cursorPosition(params: number[]): boolean { let col: number; let row: number = params[0] - 1; @@ -683,17 +698,19 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.x = col; this._terminal.buffer.y = row; + return true; } /** * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ - public cursorForwardTab(params: number[]): void { + public cursorForwardTab(params: number[]): boolean { let param = params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); } + return true; } /** @@ -736,7 +753,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[]): void { + public eraseInDisplay(params: number[]): boolean { let j; switch (params[0]) { case 0: @@ -782,6 +799,7 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } + return true; } /** @@ -795,7 +813,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[]): void { + public eraseInLine(params: number[]): boolean { switch (params[0]) { case 0: this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); @@ -808,13 +826,14 @@ export class InputHandler extends Disposable implements IInputHandler { break; } this._terminal.updateRange(this._terminal.buffer.y); + return true; } /** * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). */ - public insertLines(params: number[]): void { + public insertLines(params: number[]): boolean { let param: number = params[0]; if (param < 1) { param = 1; @@ -837,13 +856,14 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); + return true; } /** * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). */ - public deleteLines(params: number[]): void { + public deleteLines(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -867,25 +887,27 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); + return true; } /** * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). */ - public deleteChars(params: number[]): void { + public deleteChars(params: number[]): boolean { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells( this._terminal.buffer.x, params[0] || 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] ); this._terminal.updateRange(this._terminal.buffer.y); + return true; } /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public scrollUp(params: number[]): void { + public scrollUp(params: number[]): boolean { let param = params[0] || 1; // make buffer local for faster access @@ -898,12 +920,13 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); this._terminal.updateRange(buffer.scrollBottom); + return true; } /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). */ - public scrollDown(params: number[], collect?: string): void { + public scrollDown(params: number[], collect?: string): boolean { if (params.length < 2 && !collect) { let param = params[0] || 1; @@ -918,24 +941,26 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(buffer.scrollTop); this._terminal.updateRange(buffer.scrollBottom); } + return true; } /** * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). */ - public eraseChars(params: number[]): void { + public eraseChars(params: number[]): boolean { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells( this._terminal.buffer.x, this._terminal.buffer.x + (params[0] || 1), [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] ); + return true; } /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ - public cursorBackwardTab(params: number[]): void { + public cursorBackwardTab(params: number[]): boolean { let param = params[0] || 1; // make buffer local for faster access @@ -944,13 +969,14 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.x = buffer.prevStop(); } + return true; } /** * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). */ - public charPosAbsolute(params: number[]): void { + public charPosAbsolute(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -959,6 +985,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } + return true; } /** @@ -966,7 +993,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [columns] (default = [row,col+1]) (HPR) * reuse CSI Ps C ? */ - public hPositionRelative(params: number[]): void { + public hPositionRelative(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -975,12 +1002,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } + return true; } /** * CSI Ps b Repeat the preceding graphic character Ps times (REP). */ - public repeatPrecedingCharacter(params: number[]): void { + public repeatPrecedingCharacter(params: number[]): boolean { // make buffer local for faster access const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); @@ -989,6 +1017,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.get(buffer.x - 1) || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] ); // FIXME: no updateRange here? + return true; } /** @@ -1028,9 +1057,9 @@ export class InputHandler extends Disposable implements IInputHandler { * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) */ - public sendDeviceAttributes(params: number[], collect?: string): void { + public sendDeviceAttributes(params: number[], collect?: string): boolean { if (params[0] > 0) { - return; + return true; } if (!collect) { @@ -1055,13 +1084,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.handler(C0.ESC + '[>83;40003;0c'); } } + return true; } /** * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) */ - public linePosAbsolute(params: number[]): void { + public linePosAbsolute(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -1070,6 +1100,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; } + return true; } /** @@ -1077,7 +1108,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? */ - public vPositionRelative(params: number[]): void { + public vPositionRelative(params: number[]): boolean { let param = params[0]; if (param < 1) { param = 1; @@ -1090,6 +1121,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x--; } + return true; } /** @@ -1097,7 +1129,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). */ - public hVPosition(params: number[]): void { + public hVPosition(params: number[]): boolean { if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; @@ -1110,6 +1142,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } + return true; } /** @@ -1120,13 +1153,14 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html */ - public tabClear(params: number[]): void { + public tabClear(params: number[]): boolean { const param = params[0]; if (param <= 0) { delete this._terminal.buffer.tabs[this._terminal.buffer.x]; } else if (param === 3) { this._terminal.buffer.tabs = {}; } + return true; } /** @@ -1215,13 +1249,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html */ - public setMode(params: number[], collect?: string): void { + public setMode(params: number[], collect?: string): boolean { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.setMode([params[i]]); } - return; + return true; } if (!collect) { @@ -1334,6 +1368,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -1418,13 +1453,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. */ - public resetMode(params: number[], collect?: string): void { + public resetMode(params: number[], collect?: string): boolean { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.resetMode([params[i]]); } - return; + return true; } if (!collect) { @@ -1514,6 +1549,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -1581,11 +1617,11 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[]): void { + public charAttributes(params: number[]): boolean { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = DEFAULT_ATTR; - return; + return true; } const l = params.length; @@ -1705,6 +1741,8 @@ export class InputHandler extends Disposable implements IInputHandler { } this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; + + return true; } /** @@ -1730,7 +1768,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ - public deviceStatus(params: number[], collect?: string): void { + public deviceStatus(params: number[], collect?: string): boolean { if (!collect) { switch (params[0]) { case 5: @@ -1772,13 +1810,14 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html */ - public softReset(params: number[], collect?: string): void { + public softReset(params: number[], collect?: string): boolean { if (collect === '!') { this._terminal.cursorHidden = false; this._terminal.insertMode = false; @@ -1797,6 +1836,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.glevel = 0; // ?? this._terminal.charsets = [null]; // ?? } + return true; } /** @@ -1809,7 +1849,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). */ - public setCursorStyle(params?: number[], collect?: string): void { + public setCursorStyle(params?: number[], collect?: string): boolean { if (collect === ' ') { const param = params[0] < 1 ? 1 : params[0]; switch (param) { @@ -1829,6 +1869,7 @@ export class InputHandler extends Disposable implements IInputHandler { const isBlinking = param % 2 === 1; this._terminal.setOption('cursorBlink', isBlinking); } + return true; } /** @@ -1837,12 +1878,15 @@ export class InputHandler extends Disposable implements IInputHandler { * dow) (DECSTBM). * CSI ? Pm r */ - public setScrollRegion(params: number[], collect?: string): void { - if (collect) return; + public setScrollRegion(params: number[], collect?: string): boolean { + if (collect) { + return true; + } this._terminal.buffer.scrollTop = (params[0] || 1) - 1; this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; this._terminal.buffer.x = 0; this._terminal.buffer.y = 0; + return true; } @@ -1851,10 +1895,11 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 7 * Save cursor (ANSI.SYS). */ - public saveCursor(params: number[]): void { + public saveCursor(params: number[]): boolean { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.y; this._terminal.buffer.savedCurAttr = this._terminal.curAttr; + return true; } @@ -1863,10 +1908,11 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 8 * Restore cursor (ANSI.SYS). */ - public restoreCursor(params: number[]): void { + public restoreCursor(params: number[]): boolean { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR; + return true; } @@ -1875,8 +1921,9 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. */ - public setTitle(data: string): void { + public setTitle(data: string): boolean { this._terminal.handleTitle(data); + return true; } /** @@ -1885,9 +1932,10 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. */ - public nextLine(): void { + public nextLine(): boolean { this._terminal.buffer.x = 0; this.index(); + return true; } /** @@ -1895,12 +1943,13 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) * Enables the numeric keypad to send application sequences to the host. */ - public keypadApplicationMode(): void { + public keypadApplicationMode(): boolean { this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } + return true; } /** @@ -1908,12 +1957,13 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) * Enables the keypad to send numeric characters to the host. */ - public keypadNumericMode(): void { + public keypadNumericMode(): boolean { this._terminal.log('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } + return true; } /** @@ -1922,9 +1972,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Select default character set. UTF-8 is not supported (string are unicode anyways) * therefore ESC % G does the same. */ - public selectDefaultCharset(): void { + public selectDefaultCharset(): boolean { this._terminal.setgLevel(0); this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + return true; } /** @@ -1943,10 +1994,15 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC / C * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? */ - public selectCharset(collectAndFlag: string): void { - if (collectAndFlag.length !== 2) return this.selectDefaultCharset(); - if (collectAndFlag[0] === '/') return; // TODO: Is this supported? + public selectCharset(collectAndFlag: string): boolean { + if (collectAndFlag.length !== 2) { + return this.selectDefaultCharset(); + } + if (collectAndFlag[0] === '/') { + return true; // TODO: Is this supported? + } this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); + return true; } /** @@ -1955,8 +2011,9 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) * Moves the cursor down one line in the same column. */ - public index(): void { + public index(): boolean { this._terminal.index(); // TODO: save to move from terminal? + return true; } /** @@ -1966,8 +2023,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Sets a horizontal tab stop at the column position indicated by * the value of the active column when the terminal receives an HTS. */ - public tabSet(): void { + public tabSet(): boolean { this._terminal.tabSet(); // TODO: save to move from terminal? + return true; } /** @@ -1977,8 +2035,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor up one line in the same column. If the cursor is at the top margin, * the page scrolls down. */ - public reverseIndex(): void { + public reverseIndex(): boolean { this._terminal.reverseIndex(); // TODO: save to move from terminal? + return true; } /** @@ -1986,9 +2045,10 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) * Reset to initial state. */ - public reset(): void { + public reset(): boolean { this._parser.reset(); this._terminal.reset(); // TODO: save to move from terminal? + return true; } /** @@ -2001,7 +2061,8 @@ export class InputHandler extends Disposable implements IInputHandler { * When you use a locking shift, the character set remains in GL or GR until * you use another locking shift. (partly supported) */ - public setgLevel(level: number): void { + public setgLevel(level: number): boolean { this._terminal.setgLevel(level); // TODO: save to move from terminal? + return true; } } diff --git a/src/Types.ts b/src/Types.ts index cc8ff00a2..1611fb552 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -113,73 +113,73 @@ export interface IInputHandler { parse(data: string): void; print(data: Uint32Array, start: number, end: number): void; - /** C0 BEL */ bell(): void; - /** C0 LF */ lineFeed(): void; - /** C0 CR */ carriageReturn(): void; - /** C0 BS */ backspace(): void; - /** C0 HT */ tab(): void; - /** C0 SO */ shiftOut(): void; - /** C0 SI */ shiftIn(): void; + /** C0 BEL */ bell(): boolean; + /** C0 LF */ lineFeed(): boolean; + /** C0 CR */ carriageReturn(): boolean; + /** C0 BS */ backspace(): boolean; + /** C0 HT */ tab(): boolean; + /** C0 SO */ shiftOut(): boolean; + /** C0 SI */ shiftIn(): boolean; - /** CSI @ */ insertChars(params?: number[]): void; - /** CSI A */ cursorUp(params?: number[]): void; - /** CSI B */ cursorDown(params?: number[]): void; - /** CSI C */ cursorForward(params?: number[]): void; - /** CSI D */ cursorBackward(params?: number[]): void; - /** CSI E */ cursorNextLine(params?: number[]): void; - /** CSI F */ cursorPrecedingLine(params?: number[]): void; - /** CSI G */ cursorCharAbsolute(params?: number[]): void; - /** CSI H */ cursorPosition(params?: number[]): void; - /** CSI I */ cursorForwardTab(params?: number[]): void; - /** CSI J */ eraseInDisplay(params?: number[]): void; - /** CSI K */ eraseInLine(params?: number[]): void; - /** CSI L */ insertLines(params?: number[]): void; - /** CSI M */ deleteLines(params?: number[]): void; - /** CSI P */ deleteChars(params?: number[]): void; - /** CSI S */ scrollUp(params?: number[]): void; - /** CSI T */ scrollDown(params?: number[], collect?: string): void; - /** CSI X */ eraseChars(params?: number[]): void; - /** CSI Z */ cursorBackwardTab(params?: number[]): void; - /** CSI ` */ charPosAbsolute(params?: number[]): void; - /** CSI a */ hPositionRelative(params?: number[]): void; - /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; - /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void; - /** CSI d */ linePosAbsolute(params?: number[]): void; - /** CSI e */ vPositionRelative(params?: number[]): void; - /** CSI f */ hVPosition(params?: number[]): void; - /** CSI g */ tabClear(params?: number[]): void; - /** CSI h */ setMode(params?: number[], collect?: string): void; - /** CSI l */ resetMode(params?: number[], collect?: string): void; - /** CSI m */ charAttributes(params?: number[]): void; - /** CSI n */ deviceStatus(params?: number[], collect?: string): void; - /** CSI p */ softReset(params?: number[], collect?: string): void; - /** CSI q */ setCursorStyle(params?: number[], collect?: string): void; - /** CSI r */ setScrollRegion(params?: number[], collect?: string): void; - /** CSI s */ saveCursor(params?: number[]): void; - /** CSI u */ restoreCursor(params?: number[]): void; + /** CSI @ */ insertChars(params?: number[]): boolean; + /** CSI A */ cursorUp(params?: number[]): boolean; + /** CSI B */ cursorDown(params?: number[]): boolean; + /** CSI C */ cursorForward(params?: number[]): boolean; + /** CSI D */ cursorBackward(params?: number[]): boolean; + /** CSI E */ cursorNextLine(params?: number[]): boolean; + /** CSI F */ cursorPrecedingLine(params?: number[]): boolean; + /** CSI G */ cursorCharAbsolute(params?: number[]): boolean; + /** CSI H */ cursorPosition(params?: number[]): boolean; + /** CSI I */ cursorForwardTab(params?: number[]): boolean; + /** CSI J */ eraseInDisplay(params?: number[]): boolean; + /** CSI K */ eraseInLine(params?: number[]): boolean; + /** CSI L */ insertLines(params?: number[]): boolean; + /** CSI M */ deleteLines(params?: number[]): boolean; + /** CSI P */ deleteChars(params?: number[]): boolean; + /** CSI S */ scrollUp(params?: number[]): boolean; + /** CSI T */ scrollDown(params?: number[], collect?: string): boolean; + /** CSI X */ eraseChars(params?: number[]): boolean; + /** CSI Z */ cursorBackwardTab(params?: number[]): boolean; + /** CSI ` */ charPosAbsolute(params?: number[]): boolean; + /** CSI a */ hPositionRelative(params?: number[]): boolean; + /** CSI b */ repeatPrecedingCharacter(params?: number[]): boolean; + /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): boolean; + /** CSI d */ linePosAbsolute(params?: number[]): boolean; + /** CSI e */ vPositionRelative(params?: number[]): boolean; + /** CSI f */ hVPosition(params?: number[]): boolean; + /** CSI g */ tabClear(params?: number[]): boolean; + /** CSI h */ setMode(params?: number[], collect?: string): boolean; + /** CSI l */ resetMode(params?: number[], collect?: string): boolean; + /** CSI m */ charAttributes(params?: number[]): boolean; + /** CSI n */ deviceStatus(params?: number[], collect?: string): boolean; + /** CSI p */ softReset(params?: number[], collect?: string): boolean; + /** CSI q */ setCursorStyle(params?: number[], collect?: string): boolean; + /** CSI r */ setScrollRegion(params?: number[], collect?: string): boolean; + /** CSI s */ saveCursor(params?: number[]): boolean; + /** CSI u */ restoreCursor(params?: number[]): boolean; /** OSC 0 - OSC 2 */ setTitle(data: string): void; - /** ESC E */ nextLine(): void; - /** ESC = */ keypadApplicationMode(): void; - /** ESC > */ keypadNumericMode(): void; + OSC 2 */ setTitle(data: string): boolean; + /** ESC E */ nextLine(): boolean; + /** ESC = */ keypadApplicationMode(): boolean; + /** ESC > */ keypadNumericMode(): boolean; /** ESC % G - ESC % @ */ selectDefaultCharset(): void; + ESC % @ */ selectDefaultCharset(): boolean; /** ESC ( C ESC ) C ESC * C ESC + C ESC - C ESC . C - ESC / C */ selectCharset(collectAndFlag: string): void; - /** ESC D */ index(): void; - /** ESC H */ tabSet(): void; - /** ESC M */ reverseIndex(): void; - /** ESC c */ reset(): void; + ESC / C */ selectCharset(collectAndFlag: string): boolean; + /** ESC D */ index(): boolean; + /** ESC H */ tabSet(): boolean; + /** ESC M */ reverseIndex(): boolean; + /** ESC c */ reset(): boolean; /** ESC n ESC o ESC | ESC } - ESC ~ */ setgLevel(level: number): void; + ESC ~ */ setgLevel(level: number): boolean; } export interface ILinkMatcher { From 801db3831ddb7980955c2af759913aed3120f64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 1 Feb 2019 20:32:07 +0100 Subject: [PATCH 19/83] type add methods --- src/InputHandler.ts | 11 +++++++++-- src/Types.ts | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4af525f2a..05af14a24 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -435,10 +435,17 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(buffer.y); } - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { + /** + * Forward addCsiHandler from parser. + */ + public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { return this._parser.addCsiHandler(flag, callback); } - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + + /** + * Forward addOscHandler from parser. + */ + public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { return this._parser.addOscHandler(ident, callback); } diff --git a/src/Types.ts b/src/Types.ts index 1611fb552..67ad32d9c 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -112,6 +112,8 @@ export interface ICompositionHelper { export interface IInputHandler { parse(data: string): void; print(data: Uint32Array, start: number, end: number): void; + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; /** C0 BEL */ bell(): boolean; /** C0 LF */ lineFeed(): boolean; From 03e648a0fbc250438d28001036261bba951292ef Mon Sep 17 00:00:00 2001 From: ntchjb Date: Fri, 15 Mar 2019 19:15:41 +0700 Subject: [PATCH 20/83] Fixed the case that, if findPrevious is run without any selection, then start scan at the last row --- src/addons/search/SearchHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 525e25dc7..a2258e481 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -115,7 +115,7 @@ export class SearchHelper implements ISearchHelper { } const isReverseSearch = true; - let startRow = this._terminal._core.buffer.ydisp; + let startRow = this._terminal.rows-1; let startCol: number = this._terminal.cols; if (selectionManager.selectionStart) { From 3c444ca39a6c52eaacc1e7fedb3ab08a670a89d5 Mon Sep 17 00:00:00 2001 From: ntchjb Date: Fri, 15 Mar 2019 19:20:23 +0700 Subject: [PATCH 21/83] Fixed coding format --- src/addons/search/SearchHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index a2258e481..57a8c0a88 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -115,7 +115,7 @@ export class SearchHelper implements ISearchHelper { } const isReverseSearch = true; - let startRow = this._terminal.rows-1; + let startRow = this._terminal.rows - 1; let startCol: number = this._terminal.cols; if (selectionManager.selectionStart) { From ca1f7f1392203c9f601ad3f68dd818fb1bde025a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 10:11:59 -0700 Subject: [PATCH 22/83] Add EventEmitter2 --- src/common/EventEmitter2.test.ts | 31 +++++++++++++++++++++ src/common/EventEmitter2.ts | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/common/EventEmitter2.test.ts create mode 100644 src/common/EventEmitter2.ts diff --git a/src/common/EventEmitter2.test.ts b/src/common/EventEmitter2.test.ts new file mode 100644 index 000000000..53ee4c4fe --- /dev/null +++ b/src/common/EventEmitter2.test.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { EventEmitter2 } from './EventEmitter2'; + +describe('EventEmitter2', () => { + it('should fire listeners multiple times', () => { + const order: string[] = []; + const emitter = new EventEmitter2(); + emitter.event(data => order.push(data + 'a')); + emitter.event(data => order.push(data + 'b')); + emitter.fire(1); + emitter.fire(2); + assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]); + }); + + it('should not fire listeners once disposed', () => { + const order: string[] = []; + const emitter = new EventEmitter2(); + emitter.event(data => order.push(data + 'a')); + const disposeB = emitter.event(data => order.push(data + 'b')); + emitter.event(data => order.push(data + 'c')); + emitter.fire(1); + disposeB.dispose(); + emitter.fire(2); + assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]); + }); +}); diff --git a/src/common/EventEmitter2.ts b/src/common/EventEmitter2.ts new file mode 100644 index 000000000..447f816c4 --- /dev/null +++ b/src/common/EventEmitter2.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; + +type Listener = (e: T) => void; + +export interface IEvent { + (listener: (e: T) => any): IDisposable; +} + +export class EventEmitter2 { + private _listeners: Listener[] = []; + private _event?: IEvent; + + public get event(): IEvent { + if (!this._event) { + this._event = (listener: (e: T) => any) => { + this._listeners.push(listener); + const disposable = { + dispose: () => { + for (let i = 0; i < this._listeners.length; i++) { + if (this._listeners[i] === listener) { + this._listeners.splice(i, 1); + return; + } + } + } + }; + return disposable; + }; + } + return this._event; + } + + public fire(data: T): void { + const queue: Listener[] = []; + for (let i = 0; i < this._listeners.length; i++) { + queue.push(this._listeners[i]); + } + for (let i = 0; i < queue.length; i++) { + queue[i].call(undefined, data); + } + } +} From ba4662ac147968bf44494a5db0d22d66913dd762 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:01:16 -0700 Subject: [PATCH 23/83] Introduce new onEvent APIs and deprecate on/off/etc. Part of #1505 --- src/Terminal.ts | 28 +++++++++++++++ src/public/Terminal.ts | 10 ++++++ typings/xterm.d.ts | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index db696bd6f..8c69a5516 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -217,6 +218,23 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public cols: number; public rows: number; + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + private _onInput = new EventEmitter2(); + public get onInput(): IEvent { return this._onInput.event; } + private _onTitleChange = new EventEmitter2(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } + private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>(); + public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + private _onResize = new EventEmitter2<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + /** * Creates a new `Terminal` object. * @@ -235,6 +253,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II super(); this.options = clone(options); this._setup(); + + // TODO: Replace EventEmitter with EventEmitter2 internally + this.on('linefeed', () => this._onLineFeed.fire()); + this.on('selection', () => this._onSelectionChange.fire()); + this.on('data', e => this._onInput.fire(e)); + this.on('title', e => this._onTitleChange.fire(e)); + this.on('scroll', e => this._onScroll.fire(e)); + this.on('key', e => this._onKey.fire(e)); + this.on('refresh', e => this._onRender.fire(e)); + this.on('resize', e => this._onResize.fire(e)); } public dispose(): void { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef9..181beeebf 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -7,6 +7,7 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILink import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; +import { IEvent } from '../../lib/common/EventEmitter2'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -15,6 +16,15 @@ export class Terminal implements ITerminalApi { this._core = new TerminalCore(options); } + public get onLineFeed(): IEvent { return this._core.onLineFeed; } + public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } + public get onInput(): IEvent { return this._core.onInput; } + public get onTitleChange(): IEvent { return this._core.onTitleChange; } + public get onScroll(): IEvent { return this._core.onScroll; } + public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; } + public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; } + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } + public get element(): HTMLElement { return this._core.element; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d813bc5f7..0a29cfc81 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -295,6 +295,14 @@ declare module 'xterm' { dispose(): void; } + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (e: T) => any): IDisposable; + } + export interface IMarker extends IDisposable { readonly id: number; readonly isDisposed: boolean; @@ -353,6 +361,64 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; + + /** + * Adds an event listener for when a selection change occurs. + * @returns an `IDisposable` to stop listening. + */ + onSelectionChange: IEvent; + + /** + * Adds an event listener for when an input event fires. This happens for + * example when the user types or pastes into the terminal. The event value + * is whatever `string` results, in a typical setup, this should be passed + * on to the backing pty. + * @returns an `IDisposable` to stop listening. + */ + onInput: IEvent; + + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + + /** + * Adds an event listener for when a scroll occurs. The event value is the + * new position of the viewport. + * @returns an `IDisposable` to stop listening. + */ + onScroll: IEvent; + + /** + * Adds an event listener for a key is pressed. The event value contains the + * string that will be sent in the data event as well as the DOM event that + * triggered it. + * @returns an `IDisposable` to stop listening. + */ + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when rows are rendered. The event value + * contains the start row and end rows of the rendered area (ranges from `0` + * to `Terminal.rows - 1`). + * @returns an `IDisposable` to stop listening. + */ + onRender: IEvent<{ start: number, end: number }>; + + /** + * Adds an event listener for when the terminal is resized. The event value + * contains the new size. + * @returns an `IDisposable` to stop listening. + */ + onResize: IEvent<{ cols: number, rows: number }>; + /** * Unfocus the terminal. */ @@ -367,54 +433,63 @@ declare module 'xterm' { * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'data', listener: (...args: any[]) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'refresh', listener: (data: {start: number, end: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'resize', listener: (data: {cols: number, rows: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'scroll', listener: (ydisp: number) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'title', listener: (title: string) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: string, listener: (...args: any[]) => void): void; @@ -422,6 +497,7 @@ declare module 'xterm' { * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener).dispose()` instead. */ off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; @@ -439,6 +515,7 @@ declare module 'xterm' { * be used to conveniently remove the event listener. * @param type The type of event. * @param handler The event handler. + * @deprecated use `Terminal.onEvent(listener)` instead. */ addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; From 5f014be057db713055a0038379abbb4f2984d347 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:12:57 -0700 Subject: [PATCH 24/83] Add onCursorMove, replace .on usage in addons --- src/Terminal.ts | 3 +++ src/addons/search/SearchHelper.ts | 9 +++++++-- src/addons/terminado/terminado.ts | 4 ++-- src/addons/winptyCompat/winptyCompat.ts | 2 +- src/public/Terminal.ts | 1 + src/ui/TestUtils.test.ts | 11 ++++++++++- typings/xterm.d.ts | 6 ++++++ 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 8c69a5516..1f05ec2e4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -218,6 +218,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public cols: number; public rows: number; + private _onCursorMove = new EventEmitter2(); + public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onSelectionChange = new EventEmitter2(); @@ -255,6 +257,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally + this.on('cursormove', () => this._onCursorMove.fire()); this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 96cd845d4..7562efdf7 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -4,6 +4,7 @@ */ import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces'; +import { IDisposable } from 'xterm'; const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs @@ -19,6 +20,7 @@ export class SearchHelper implements ISearchHelper { */ private _linesCache: string[] = null; private _linesCacheTimeoutId = 0; + private _cursorMoveListener: IDisposable | undefined; constructor(private _terminal: ISearchAddonTerminal) { this._destroyLinesCache = this._destroyLinesCache.bind(this); @@ -148,7 +150,7 @@ export class SearchHelper implements ISearchHelper { private _initLinesCache(): void { if (!this._linesCache) { this._linesCache = new Array(this._terminal._core.buffer.length); - this._terminal.on('cursormove', this._destroyLinesCache); + this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); } window.clearTimeout(this._linesCacheTimeoutId); @@ -157,7 +159,10 @@ export class SearchHelper implements ISearchHelper { private _destroyLinesCache(): void { this._linesCache = null; - this._terminal.off('cursormove', this._destroyLinesCache); + if (this._cursorMoveListener) { + this._cursorMoveListener.dispose(); + this._cursorMoveListener = undefined; + } if (this._linesCacheTimeoutId) { window.clearTimeout(this._linesCacheTimeoutId); this._linesCacheTimeoutId = 0; diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index 136eea8e8..b36c2cbfa 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,9 +59,9 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal.on('data', addonTerminal.__sendData); + addonTerminal.onInput(addonTerminal.__sendData); } - addonTerminal.on('resize', addonTerminal.__setSize); + addonTerminal.onResize(addonTerminal.__setSize); socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket)); socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket)); diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 58f59fd90..590446134 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -25,7 +25,7 @@ export function winptyCompatInit(terminal: Terminal): void { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - addonTerminal.on('linefeed', () => { + addonTerminal.onLineFeed(() => { const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); const lastChar = line.get(addonTerminal.cols - 1); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 181beeebf..60b275f39 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -16,6 +16,7 @@ export class Terminal implements ITerminalApi { this._core = new TerminalCore(options); } + public get onCursorMove(): IEvent { return this._core.onCursorMove; } public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } public get onInput(): IEvent { return this._core.onInput; } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9d525fbf9..2e23fe703 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuff import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../core/Platform'; -import { ITheme, IDisposable, IMarker } from 'xterm'; +import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; import { Terminal } from '../Terminal'; export class TestTerminal extends Terminal { @@ -19,6 +19,15 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + onCursorMove: IEvent; + onLineFeed: IEvent; + onSelectionChange: IEvent; + onInput: IEvent; + onTitleChange: IEvent; + onScroll: IEvent; + onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; + onRender: IEvent<{ start: number; end: number; }>; + onResize: IEvent<{ cols: number; rows: number; }>; markers: IMarker[]; addMarker(cursorYOffset: number): IMarker { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0a29cfc81..e51c11679 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -361,6 +361,12 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for the cursor moves. + * @returns an `IDisposable` to stop listening. + */ + onCursorMove: IEvent; + /** * Adds an event listener for when a line feed is added. * @returns an `IDisposable` to stop listening. From f8fb4da9f49585389f53432ed1b0eaaf66f1fa10 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:30:47 -0700 Subject: [PATCH 25/83] Replace EventEmitter with EventEmitter2 in CircularList --- src/Buffer.test.ts | 4 ++-- src/Buffer.ts | 14 +++++++------- src/BufferReflow.ts | 6 +++--- src/SelectionManager.ts | 14 ++++++++------ src/common/CircularList.ts | 22 ++++++++++++++-------- src/common/Types.ts | 11 ++++++++++- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 59475adb7..72c07dc76 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -1025,7 +1025,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.emit('trim', 1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1036,7 +1036,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.emit('trim', 1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9cc1adba8..790667b6e 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList'; +import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; @@ -442,7 +442,7 @@ export class Buffer implements IBuffer { insertEvents.push({ index: originalLineIndex + 1, amount: nextToInsert.newLines.length - } as IInsertEvent); + }); countInsertedSoFar += nextToInsert.newLines.length; nextToInsert = toInsert[++nextToInsertIndex]; @@ -455,12 +455,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.emit('insert', insertEvents[i]); + this.lines.onInsertEmitter.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.emitMayRemoveListeners('trim', amountToTrim); + this.lines.onTrimEmitter.fire(amountToTrim); } } } @@ -580,19 +580,19 @@ export class Buffer implements IBuffer { public addMarker(y: number): Marker { const marker = new Marker(y); this.markers.push(marker); - marker.register(this.lines.addDisposableListener('trim', amount => { + marker.register(this.lines.onTrim(amount => { marker.line -= amount; // The marker should be disposed when the line is trimmed from the buffer if (marker.line < 0) { marker.dispose(); } })); - marker.register(this.lines.addDisposableListener('insert', (event: IInsertEvent) => { + marker.register(this.lines.onInsert(event => { if (marker.line >= event.index) { marker.line += event.amount; } })); - marker.register(this.lines.addDisposableListener('delete', (event: IDeleteEvent) => { + marker.register(this.lines.onDelete(event => { // Delete the marker if it's within the range if (marker.line >= event.index && marker.line < event.index + event.amount) { marker.dispose(); diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index d27d7c485..afe336d0f 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -4,7 +4,7 @@ */ import { BufferLine, CellData } from './BufferLine'; -import { CircularList, IDeleteEvent } from './common/CircularList'; +import { CircularList } from './common/CircularList'; import { IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; @@ -120,10 +120,10 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.emit('delete', { + lines.onDeleteEmitter.fire({ index: i - countRemovedSoFar, amount: countToRemove - } as IDeleteEvent); + }); i += countToRemove - 1; countRemovedSoFar += countToRemove; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 361b11235..702f6790b 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -4,7 +4,6 @@ */ import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types'; -import { XtermListener } from './common/Types'; import { MouseHelper } from './ui/MouseHelper'; import * as Browser from './core/Platform'; import { CharMeasure } from './ui/CharMeasure'; @@ -12,6 +11,7 @@ import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; +import { IDisposable } from 'xterm'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -102,7 +102,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseMoveListener: EventListener; private _mouseUpListener: EventListener; - private _trimListener: XtermListener; + private _trimListener: IDisposable; private _workCell: CellData = new CellData(); private _mouseDownTimeStamp: number; @@ -134,13 +134,12 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _initListeners(): void { this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - this._trimListener = (amount: number) => this._onTrim(amount); this.initBuffersListeners(); } public initBuffersListeners(): void { - this._terminal.buffer.lines.on('trim', this._trimListener); + this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); this._terminal.buffers.on('activate', e => this._onBufferActivate(e)); } @@ -337,6 +336,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param amount The amount the buffer is being trimmed. */ private _onTrim(amount: number): void { + console.log('onTrim', amount); const needsRefresh = this._model.onTrim(amount); if (needsRefresh) { this.refresh(); @@ -657,8 +657,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be // beneficial to leave the selection as is for these cases. - e.inactiveBuffer.lines.off('trim', this._trimListener); - e.activeBuffer.lines.on('trim', this._trimListener); + if (this._trimListener) { + this._trimListener.dispose(); + } + this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount)); } /** diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 90891b72a..d4fc41cda 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { EventEmitter } from './EventEmitter'; import { ICircularList } from './Types'; +import { EventEmitter2, IEvent } from './EventEmitter2'; export interface IInsertEvent { index: number; @@ -20,15 +20,21 @@ export interface IDeleteEvent { * Represents a circular list; a list with a maximum size that wraps around when push is called, * overriding values at the start of the list. */ -export class CircularList extends EventEmitter implements ICircularList { +export class CircularList implements ICircularList { protected _array: (T | undefined)[]; private _startIndex: number; private _length: number; + public onDeleteEmitter = new EventEmitter2(); + public get onDelete(): IEvent { return this.onDeleteEmitter.event; } + public onInsertEmitter = new EventEmitter2(); + public get onInsert(): IEvent { return this.onInsertEmitter.event; } + public onTrimEmitter = new EventEmitter2(); + public get onTrim(): IEvent { return this.onTrimEmitter.event; } + constructor( private _maxLength: number ) { - super(); this._array = new Array(this._maxLength); this._startIndex = 0; this._length = 0; @@ -101,7 +107,7 @@ export class CircularList extends EventEmitter implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); } else { this._length++; } @@ -117,7 +123,7 @@ export class CircularList extends EventEmitter implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -167,7 +173,7 @@ export class CircularList extends EventEmitter implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.emitMayRemoveListeners('trim', countToTrim); + this.onTrimEmitter.fire(countToTrim); } else { this._length += items.length; } @@ -183,7 +189,7 @@ export class CircularList extends EventEmitter implements ICircularList { } this._startIndex += count; this._length -= count; - this.emitMayRemoveListeners('trim', count); + this.onTrimEmitter.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -207,7 +213,7 @@ export class CircularList extends EventEmitter implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); } } } else { diff --git a/src/common/Types.ts b/src/common/Types.ts index 8a416bf1a..c38b9c164 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -4,6 +4,8 @@ */ import { IEventEmitter } from 'xterm'; +import { IEvent, EventEmitter2 } from './EventEmitter2'; +import { IDeleteEvent, IInsertEvent } from './CircularList'; export type XtermListener = (...args: any[]) => void; @@ -21,11 +23,18 @@ export interface IKeyboardEvent { type: string; } -export interface ICircularList extends IEventEmitter { +export interface ICircularList { length: number; maxLength: number; isFull: boolean; + onDeleteEmitter: EventEmitter2; + onDelete: IEvent; + onInsertEmitter: EventEmitter2; + onInsert: IEvent; + onTrimEmitter: EventEmitter2; + onTrim: IEvent; + get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; From 60c2b20d68bdf771b2f2a6c43eab6d5b7b619355 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:35:20 -0700 Subject: [PATCH 26/83] Replace EventEmitter usage in BufferSet --- src/BufferSet.ts | 15 +++++++++------ src/SelectionManager.ts | 3 +-- src/Types.ts | 5 ++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/BufferSet.ts b/src/BufferSet.ts index f84757d19..ffa5bc909 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,25 +3,28 @@ * @license MIT */ -import { ITerminal, IBufferSet } from './Types'; +import { ITerminal, IBufferSet, IBuffer } from './Types'; import { Buffer } from './Buffer'; -import { EventEmitter } from './common/EventEmitter'; +import { EventEmitter2, IEvent } from '../lib/common/EventEmitter2'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and * provides also utilities for working with them. */ -export class BufferSet extends EventEmitter implements IBufferSet { +export class BufferSet implements IBufferSet { private _normal: Buffer; private _alt: Buffer; private _activeBuffer: Buffer; + + private _onBufferActivate = new EventEmitter2<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>(); + public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } + /** * Create a new BufferSet for the given terminal. * @param _terminal - The terminal the BufferSet will belong to */ constructor(private _terminal: ITerminal) { - super(); this._normal = new Buffer(this._terminal, true); this._normal.fillViewportRows(); @@ -68,7 +71,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { // when activated. this._alt.clear(); this._activeBuffer = this._normal; - this.emit('activate', { + this._onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -87,7 +90,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this.emit('activate', { + this._onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 702f6790b..789a4bbf7 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -140,7 +140,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public initBuffersListeners(): void { this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); - this._terminal.buffers.on('activate', e => this._onBufferActivate(e)); + this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e)); } /** @@ -336,7 +336,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param amount The amount the buffer is being trimmed. */ private _onTrim(amount: number): void { - console.log('onTrim', amount); const needsRefresh = this._model.onTrim(amount); if (needsRefresh) { this.refresh(); diff --git a/src/Types.ts b/src/Types.ts index 10665f258..a4da0d543 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,6 +8,7 @@ import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; +import { IEvent } from '../lib/common/EventEmitter2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -302,11 +303,13 @@ export interface IBuffer { getWhitespaceCell(fg?: number, bg?: number): ICellData; } -export interface IBufferSet extends IEventEmitter { +export interface IBufferSet { alt: IBuffer; normal: IBuffer; active: IBuffer; + onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; + activateNormalBuffer(): void; activateAltBuffer(fillAttr?: number): void; } From 60113fa155f5302ac7c6c9b2e662a6e3eb54269f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:38:55 -0700 Subject: [PATCH 27/83] Replace EventEmitter usage in CharMeasure --- src/Terminal.ts | 2 +- src/Types.ts | 3 +++ src/ui/CharMeasure.ts | 10 ++++++---- src/ui/TestUtils.test.ts | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1f05ec2e4..991ede25c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -748,7 +748,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); - this.register(this.charMeasure.addDisposableListener('charsizechanged', () => this.renderer.onCharSizeChanged())); + this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); diff --git a/src/Types.ts b/src/Types.ts index a4da0d543..38947af5c 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -253,6 +253,9 @@ export interface IMouseHelper { export interface ICharMeasure { width: number; height: number; + + onCharSizeChanged: IEvent; + measure(options: ITerminalOptions): void; } diff --git a/src/ui/CharMeasure.ts b/src/ui/CharMeasure.ts index 0ac755eab..c24e82a0d 100644 --- a/src/ui/CharMeasure.ts +++ b/src/ui/CharMeasure.ts @@ -4,22 +4,24 @@ */ import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter } from '../common/EventEmitter'; +import { EventEmitter2, IEvent } from '../../lib/common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in * the DOM rather than with a canvas context because support for extracting the * height of characters is patchy across browsers. */ -export class CharMeasure extends EventEmitter implements ICharMeasure { +export class CharMeasure implements ICharMeasure { private _document: Document; private _parentElement: HTMLElement; private _measureElement: HTMLElement; private _width: number; private _height: number; + private _onCharSizeChanged = new EventEmitter2(); + public get onCharSizeChanged(): IEvent { return this._onCharSizeChanged.event; } + constructor(document: Document, parentElement: HTMLElement) { - super(); this._document = document; this._parentElement = parentElement; this._measureElement = this._document.createElement('span'); @@ -50,7 +52,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { if (this._width !== geometry.width || this._height !== adjustedHeight) { this._width = geometry.width; this._height = adjustedHeight; - this.emit('charsizechanged'); + this._onCharSizeChanged.fire(); } } } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 2e23fe703..0e71f1a31 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -174,6 +174,7 @@ export class MockTerminal implements ITerminal { } export class MockCharMeasure implements ICharMeasure { + onCharSizeChanged: IEvent; width: number; height: number; measure(options: ITerminalOptions): void { From 7b9a718af01dc5edfefae4daae00383c8a19ea03 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:40:19 -0700 Subject: [PATCH 28/83] Fix some imports to point at ts files, not lib/ --- src/BufferSet.ts | 2 +- src/Types.ts | 2 +- src/public/Terminal.ts | 2 +- src/ui/CharMeasure.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BufferSet.ts b/src/BufferSet.ts index ffa5bc909..9eb14addf 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -5,7 +5,7 @@ import { ITerminal, IBufferSet, IBuffer } from './Types'; import { Buffer } from './Buffer'; -import { EventEmitter2, IEvent } from '../lib/common/EventEmitter2'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and diff --git a/src/Types.ts b/src/Types.ts index 38947af5c..c9e519395 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,7 +8,7 @@ import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; -import { IEvent } from '../lib/common/EventEmitter2'; +import { IEvent } from './common/EventEmitter2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 60b275f39..53c3c693f 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -7,7 +7,7 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILink import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; -import { IEvent } from '../../lib/common/EventEmitter2'; +import { IEvent } from '../common/EventEmitter2'; export class Terminal implements ITerminalApi { private _core: ITerminal; diff --git a/src/ui/CharMeasure.ts b/src/ui/CharMeasure.ts index c24e82a0d..c37df312a 100644 --- a/src/ui/CharMeasure.ts +++ b/src/ui/CharMeasure.ts @@ -4,7 +4,7 @@ */ import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter2, IEvent } from '../../lib/common/EventEmitter2'; +import { EventEmitter2, IEvent } from '../common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in From 4e6f4be6cce7e9b99cc894fbce4b23869dc5ebe3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:14:04 -0400 Subject: [PATCH 29/83] Convert Listener to an interface --- src/common/EventEmitter2.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/EventEmitter2.ts b/src/common/EventEmitter2.ts index a60c58368..71c0b72dd 100644 --- a/src/common/EventEmitter2.ts +++ b/src/common/EventEmitter2.ts @@ -5,14 +5,16 @@ import { IDisposable } from './Types'; -type Listener = (e: T) => void; +interface IListener { + (e: T): void; +} export interface IEvent { (listener: (e: T) => any): IDisposable; } export class EventEmitter2 { - private _listeners: Listener[] = []; + private _listeners: IListener[] = []; private _event?: IEvent; public get event(): IEvent { @@ -36,7 +38,7 @@ export class EventEmitter2 { } public fire(data: T): void { - const queue: Listener[] = []; + const queue: IListener[] = []; for (let i = 0; i < this._listeners.length; i++) { queue.push(this._listeners[i]); } From f35dfb496aa46cc95dac6cd484aa5ad034ae78aa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:19:08 -0400 Subject: [PATCH 30/83] Use onKey in demo --- demo/client.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7a6018984..8a3e4d56b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -165,8 +165,9 @@ function runFakeTerminal(): void { term.writeln(''); term.prompt(); - term._core.register(term.addDisposableListener('key', (key, ev) => { - const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey; + term.onKey((e: { key: string, domEvent: KeyboardEvent }) => { + const ev = e.domEvent; + const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; if (ev.keyCode === 13) { term.prompt(); @@ -176,9 +177,9 @@ function runFakeTerminal(): void { term.write('\b \b'); } } else if (printable) { - term.write(key); + term.write(e.key); } - })); + }); term._core.register(term.addDisposableListener('paste', (data, ev) => { term.write(data); From 3f9d90ecf65bf049b233d3fc6776b94c1c4425a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:23:12 -0400 Subject: [PATCH 31/83] Remove unnecessary paste listener from demo --- demo/client.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 8a3e4d56b..f631a917a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -180,10 +180,6 @@ function runFakeTerminal(): void { term.write(e.key); } }); - - term._core.register(term.addDisposableListener('paste', (data, ev) => { - term.write(data); - })); } function initOptions(term: TerminalType): void { From 629238ae700b259ac8fe4e5e6303354648927806 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:25:03 -0400 Subject: [PATCH 32/83] Use onResize in demo --- demo/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index f631a917a..c73d81ddc 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -86,7 +86,7 @@ function createTerminal(): void { windowsMode: isWindows } as ITerminalOptions); window.term = term; // Expose `term` to window for debugging purposes - term.on('resize', (size: { cols: number, rows: number }) => { + term.onResize((size: { cols: number, rows: number }) => { if (!pid) { return; } From 2f8d49c74df812e8c4a5a90b0f5b44c5d3dcb2d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:45:57 -0400 Subject: [PATCH 33/83] Convert internal key event usages --- src/AccessibilityManager.ts | 2 +- src/Terminal.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 877676c97..a8e0ba1bc 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -79,7 +79,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); - this.register(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar))); + this.register(this._terminal.onKey(e => this._onKey(e.key))); this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always diff --git a/src/Terminal.ts b/src/Terminal.ts index f6a987f7f..5d2e96ded 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -266,9 +266,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.on('data', e => this._onInput.fire(e)); this.on('title', e => this._onTitleChange.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); - this.on('key', e => this._onKey.fire(e)); this.on('refresh', e => this._onRender.fire(e)); this.on('resize', e => this._onResize.fire(e)); + + // TODO: Remove these in v4 + // Fire old style events from new emitters + this.onKey(e => this.emit('key', e.key, e.domEvent)); } public dispose(): void { @@ -1615,7 +1618,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } this.emit('keydown', event); - this.emit('key', result.key, event); + this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); this.handler(result.key); @@ -1694,7 +1697,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II key = String.fromCharCode(key); this.emit('keypress', key, ev); - this.emit('key', key, ev); + this._onKey.fire({ key, domEvent: ev }); this.showCursor(); this.handler(key); From 7e36bf47c679f639027bd693862bed83e1793e1f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 6 Apr 2019 15:21:04 -0400 Subject: [PATCH 34/83] Have undefined | true mean success --- src/EscapeSequenceParser.ts | 8 +- src/InputHandler.ts | 179 +++++++++++++----------------------- src/Types.ts | 108 +++++++++++----------- 3 files changed, 122 insertions(+), 173 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 7b65624d2..197a36570 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -353,7 +353,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (this._oscHandlers[ident] === undefined) { this._oscHandlers[ident] = []; } - const handlerList = this._oscHandlers[ident]; + const handlerList = this._oscHandlers[ident]; handlerList.push(callback); return { dispose: () => { @@ -505,7 +505,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP const handlers = this._csiHandlers[code]; let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { - if (handlers[j](params, collect)) { + // undefined or true means success and to stop bubbling + if (handlers[j](params, collect) !== false) { break; } } @@ -590,7 +591,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP const handlers = this._oscHandlers[identifier]; let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { - if (handlers[j](content)) { + // undefined or true means success and to stop bubbling + if (handlers[j](content) !== false) { break; } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e8d6d9065..33e2db56e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -439,16 +439,15 @@ export class InputHandler extends Disposable implements IInputHandler { * BEL * Bell (Ctrl-G). */ - public bell(): boolean { + public bell(): void { this._terminal.bell(); - return true; } /** * LF * Line Feed or New Line (NL). (LF is Ctrl-J). */ - public lineFeed(): boolean { + public lineFeed(): void { // make buffer local for faster access const buffer = this._terminal.buffer; @@ -470,40 +469,36 @@ export class InputHandler extends Disposable implements IInputHandler { * @event linefeed */ this._terminal.emit('linefeed'); - return true; } /** * CR * Carriage Return (Ctrl-M). */ - public carriageReturn(): boolean { + public carriageReturn(): void { this._terminal.buffer.x = 0; - return true; } /** * BS * Backspace (Ctrl-H). */ - public backspace(): boolean { + public backspace(): void { if (this._terminal.buffer.x > 0) { this._terminal.buffer.x--; } - return true; } /** * TAB * Horizontal Tab (HT) (Ctrl-I). */ - public tab(): boolean { + public tab(): void { const originalX = this._terminal.buffer.x; this._terminal.buffer.x = this._terminal.buffer.nextStop(); if (this._terminal.options.screenReaderMode) { this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); } - return true; } /** @@ -511,9 +506,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. */ - public shiftOut(): boolean { + public shiftOut(): void { this._terminal.setgLevel(1); - return true; } /** @@ -521,30 +515,28 @@ export class InputHandler extends Disposable implements IInputHandler { * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). */ - public shiftIn(): boolean { + public shiftIn(): void { this._terminal.setgLevel(0); - return true; } /** * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ - public insertChars(params: number[]): boolean { + public insertChars(params: number[]): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); this._terminal.updateRange(this._terminal.buffer.y); - return true; } /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ - public cursorUp(params: number[]): boolean { + public cursorUp(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -553,14 +545,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.y < 0) { this._terminal.buffer.y = 0; } - return true; } /** * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[]): boolean { + public cursorDown(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -573,14 +564,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x--; } - return true; } /** * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[]): boolean { + public cursorForward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -589,14 +579,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } - return true; } /** * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[]): boolean { + public cursorBackward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -609,7 +598,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x < 0) { this._terminal.buffer.x = 0; } - return true; } /** @@ -617,7 +605,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * same as CSI Ps B ? */ - public cursorNextLine(params: number[]): boolean { + public cursorNextLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -627,7 +615,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = this._terminal.rows - 1; } this._terminal.buffer.x = 0; - return true; } @@ -636,7 +623,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Preceding Line Ps Times (default = 1) (CNL). * reuse CSI Ps A ? */ - public cursorPrecedingLine(params: number[]): boolean { + public cursorPrecedingLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -646,7 +633,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = 0; } this._terminal.buffer.x = 0; - return true; } @@ -654,20 +640,19 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). */ - public cursorCharAbsolute(params: number[]): boolean { + public cursorCharAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } this._terminal.buffer.x = param - 1; - return true; } /** * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ - public cursorPosition(params: number[]): boolean { + public cursorPosition(params: number[]): void { let col: number; let row: number = params[0] - 1; @@ -691,19 +676,17 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.x = col; this._terminal.buffer.y = row; - return true; } /** * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ - public cursorForwardTab(params: number[]): boolean { + public cursorForwardTab(params: number[]): void { let param = params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); } - return true; } /** @@ -746,7 +729,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[]): boolean { + public eraseInDisplay(params: number[]): void { let j; switch (params[0]) { case 0: @@ -792,7 +775,6 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } - return true; } /** @@ -806,7 +788,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[]): boolean { + public eraseInLine(params: number[]): void { switch (params[0]) { case 0: this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); @@ -819,14 +801,13 @@ export class InputHandler extends Disposable implements IInputHandler { break; } this._terminal.updateRange(this._terminal.buffer.y); - return true; } /** * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). */ - public insertLines(params: number[]): boolean { + public insertLines(params: number[]): void { let param: number = params[0]; if (param < 1) { param = 1; @@ -849,14 +830,13 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); - return true; } /** * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). */ - public deleteLines(params: number[]): boolean { + public deleteLines(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -880,27 +860,25 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); - return true; } /** * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). */ - public deleteChars(params: number[]): boolean { + public deleteChars(params: number[]): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells( this._terminal.buffer.x, params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); this._terminal.updateRange(this._terminal.buffer.y); - return true; } /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public scrollUp(params: number[]): boolean { + public scrollUp(params: number[]): void { let param = params[0] || 1; // make buffer local for faster access @@ -913,13 +891,12 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); this._terminal.updateRange(buffer.scrollBottom); - return true; } /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). */ - public scrollDown(params: number[], collect?: string): boolean { + public scrollDown(params: number[], collect?: string): void { if (params.length < 2 && !collect) { let param = params[0] || 1; @@ -934,26 +911,24 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(buffer.scrollTop); this._terminal.updateRange(buffer.scrollBottom); } - return true; } /** * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). */ - public eraseChars(params: number[]): boolean { + public eraseChars(params: number[]): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells( this._terminal.buffer.x, this._terminal.buffer.x + (params[0] || 1), this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); - return true; } /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ - public cursorBackwardTab(params: number[]): boolean { + public cursorBackwardTab(params: number[]): void { let param = params[0] || 1; // make buffer local for faster access @@ -962,14 +937,13 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.x = buffer.prevStop(); } - return true; } /** * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). */ - public charPosAbsolute(params: number[]): boolean { + public charPosAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -978,7 +952,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } - return true; } /** @@ -986,7 +959,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [columns] (default = [row,col+1]) (HPR) * reuse CSI Ps C ? */ - public hPositionRelative(params: number[]): boolean { + public hPositionRelative(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -995,13 +968,12 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } - return true; } /** * CSI Ps b Repeat the preceding graphic character Ps times (REP). */ - public repeatPrecedingCharacter(params: number[]): boolean { + public repeatPrecedingCharacter(params: number[]): void { // make buffer local for faster access const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); @@ -1011,7 +983,6 @@ export class InputHandler extends Disposable implements IInputHandler { (this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(DEFAULT_ATTR) ); // FIXME: no updateRange here? - return true; } /** @@ -1051,9 +1022,9 @@ export class InputHandler extends Disposable implements IInputHandler { * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) */ - public sendDeviceAttributes(params: number[], collect?: string): boolean { + public sendDeviceAttributes(params: number[], collect?: string): void { if (params[0] > 0) { - return true; + return; } if (!collect) { @@ -1078,14 +1049,13 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.handler(C0.ESC + '[>83;40003;0c'); } } - return true; } /** * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) */ - public linePosAbsolute(params: number[]): boolean { + public linePosAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -1094,7 +1064,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; } - return true; } /** @@ -1102,7 +1071,7 @@ export class InputHandler extends Disposable implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? */ - public vPositionRelative(params: number[]): boolean { + public vPositionRelative(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -1115,7 +1084,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x--; } - return true; } /** @@ -1123,7 +1091,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). */ - public hVPosition(params: number[]): boolean { + public hVPosition(params: number[]): void { if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; @@ -1136,7 +1104,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } - return true; } /** @@ -1147,14 +1114,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html */ - public tabClear(params: number[]): boolean { + public tabClear(params: number[]): void { const param = params[0]; if (param <= 0) { delete this._terminal.buffer.tabs[this._terminal.buffer.x]; } else if (param === 3) { this._terminal.buffer.tabs = {}; } - return true; } /** @@ -1243,13 +1209,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html */ - public setMode(params: number[], collect?: string): boolean { + public setMode(params: number[], collect?: string): void { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.setMode([params[i]]); } - return true; + return; } if (!collect) { @@ -1364,7 +1330,6 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } - return true; } /** @@ -1449,13 +1414,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. */ - public resetMode(params: number[], collect?: string): boolean { + public resetMode(params: number[], collect?: string): void { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.resetMode([params[i]]); } - return true; + return; } if (!collect) { @@ -1547,7 +1512,6 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } - return true; } /** @@ -1615,11 +1579,11 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[]): boolean { + public charAttributes(params: number[]): void { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = DEFAULT_ATTR; - return true; + return; } const l = params.length; @@ -1739,8 +1703,6 @@ export class InputHandler extends Disposable implements IInputHandler { } this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; - - return true; } /** @@ -1766,7 +1728,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ - public deviceStatus(params: number[], collect?: string): boolean { + public deviceStatus(params: number[], collect?: string): void { if (!collect) { switch (params[0]) { case 5: @@ -1808,14 +1770,13 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } - return true; } /** * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html */ - public softReset(params: number[], collect?: string): boolean { + public softReset(params: number[], collect?: string): void { if (collect === '!') { this._terminal.cursorHidden = false; this._terminal.insertMode = false; @@ -1834,7 +1795,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.glevel = 0; // ?? this._terminal.charsets = [null]; // ?? } - return true; } /** @@ -1847,7 +1807,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). */ - public setCursorStyle(params?: number[], collect?: string): boolean { + public setCursorStyle(params?: number[], collect?: string): void { if (collect === ' ') { const param = params[0] < 1 ? 1 : params[0]; switch (param) { @@ -1867,7 +1827,6 @@ export class InputHandler extends Disposable implements IInputHandler { const isBlinking = param % 2 === 1; this._terminal.setOption('cursorBlink', isBlinking); } - return true; } /** @@ -1876,15 +1835,14 @@ export class InputHandler extends Disposable implements IInputHandler { * dow) (DECSTBM). * CSI ? Pm r */ - public setScrollRegion(params: number[], collect?: string): boolean { + public setScrollRegion(params: number[], collect?: string): void { if (collect) { - return true; + return; } this._terminal.buffer.scrollTop = (params[0] || 1) - 1; this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; this._terminal.buffer.x = 0; this._terminal.buffer.y = 0; - return true; } @@ -1893,11 +1851,10 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 7 * Save cursor (ANSI.SYS). */ - public saveCursor(params: number[]): boolean { + public saveCursor(params: number[]): void { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.y; this._terminal.buffer.savedCurAttr = this._terminal.curAttr; - return true; } @@ -1906,11 +1863,10 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 8 * Restore cursor (ANSI.SYS). */ - public restoreCursor(params: number[]): boolean { + public restoreCursor(params: number[]): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR; - return true; } @@ -1919,9 +1875,8 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. */ - public setTitle(data: string): boolean { + public setTitle(data: string): void { this._terminal.handleTitle(data); - return true; } /** @@ -1930,10 +1885,9 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. */ - public nextLine(): boolean { + public nextLine(): void { this._terminal.buffer.x = 0; this.index(); - return true; } /** @@ -1941,13 +1895,12 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) * Enables the numeric keypad to send application sequences to the host. */ - public keypadApplicationMode(): boolean { + public keypadApplicationMode(): void { this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } - return true; } /** @@ -1955,13 +1908,12 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) * Enables the keypad to send numeric characters to the host. */ - public keypadNumericMode(): boolean { + public keypadNumericMode(): void { this._terminal.log('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } - return true; } /** @@ -1970,10 +1922,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Select default character set. UTF-8 is not supported (string are unicode anyways) * therefore ESC % G does the same. */ - public selectDefaultCharset(): boolean { + public selectDefaultCharset(): void { this._terminal.setgLevel(0); this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - return true; } /** @@ -1992,15 +1943,16 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC / C * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? */ - public selectCharset(collectAndFlag: string): boolean { + public selectCharset(collectAndFlag: string): void { if (collectAndFlag.length !== 2) { - return this.selectDefaultCharset(); + this.selectDefaultCharset(); + return; } if (collectAndFlag[0] === '/') { - return true; // TODO: Is this supported? + return; // TODO: Is this supported? } this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); - return true; + return; } /** @@ -2009,9 +1961,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) * Moves the cursor down one line in the same column. */ - public index(): boolean { + public index(): void { this._terminal.index(); // TODO: save to move from terminal? - return true; } /** @@ -2021,9 +1972,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Sets a horizontal tab stop at the column position indicated by * the value of the active column when the terminal receives an HTS. */ - public tabSet(): boolean { + public tabSet(): void { this._terminal.tabSet(); // TODO: save to move from terminal? - return true; } /** @@ -2033,9 +1983,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor up one line in the same column. If the cursor is at the top margin, * the page scrolls down. */ - public reverseIndex(): boolean { + public reverseIndex(): void { this._terminal.reverseIndex(); // TODO: save to move from terminal? - return true; } /** @@ -2043,10 +1992,9 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) * Reset to initial state. */ - public reset(): boolean { + public reset(): void { this._parser.reset(); this._terminal.reset(); // TODO: save to move from terminal? - return true; } /** @@ -2059,8 +2007,7 @@ export class InputHandler extends Disposable implements IInputHandler { * When you use a locking shift, the character set remains in GL or GR until * you use another locking shift. (partly supported) */ - public setgLevel(level: number): boolean { + public setgLevel(level: number): void { this._terminal.setgLevel(level); // TODO: save to move from terminal? - return true; } } diff --git a/src/Types.ts b/src/Types.ts index a21ac59ee..cbb8e502e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -115,73 +115,73 @@ export interface IInputHandler { addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - /** C0 BEL */ bell(): boolean; - /** C0 LF */ lineFeed(): boolean; - /** C0 CR */ carriageReturn(): boolean; - /** C0 BS */ backspace(): boolean; - /** C0 HT */ tab(): boolean; - /** C0 SO */ shiftOut(): boolean; - /** C0 SI */ shiftIn(): boolean; + /** C0 BEL */ bell(): void; + /** C0 LF */ lineFeed(): void; + /** C0 CR */ carriageReturn(): void; + /** C0 BS */ backspace(): void; + /** C0 HT */ tab(): void; + /** C0 SO */ shiftOut(): void; + /** C0 SI */ shiftIn(): void; - /** CSI @ */ insertChars(params?: number[]): boolean; - /** CSI A */ cursorUp(params?: number[]): boolean; - /** CSI B */ cursorDown(params?: number[]): boolean; - /** CSI C */ cursorForward(params?: number[]): boolean; - /** CSI D */ cursorBackward(params?: number[]): boolean; - /** CSI E */ cursorNextLine(params?: number[]): boolean; - /** CSI F */ cursorPrecedingLine(params?: number[]): boolean; - /** CSI G */ cursorCharAbsolute(params?: number[]): boolean; - /** CSI H */ cursorPosition(params?: number[]): boolean; - /** CSI I */ cursorForwardTab(params?: number[]): boolean; - /** CSI J */ eraseInDisplay(params?: number[]): boolean; - /** CSI K */ eraseInLine(params?: number[]): boolean; - /** CSI L */ insertLines(params?: number[]): boolean; - /** CSI M */ deleteLines(params?: number[]): boolean; - /** CSI P */ deleteChars(params?: number[]): boolean; - /** CSI S */ scrollUp(params?: number[]): boolean; - /** CSI T */ scrollDown(params?: number[], collect?: string): boolean; - /** CSI X */ eraseChars(params?: number[]): boolean; - /** CSI Z */ cursorBackwardTab(params?: number[]): boolean; - /** CSI ` */ charPosAbsolute(params?: number[]): boolean; - /** CSI a */ hPositionRelative(params?: number[]): boolean; - /** CSI b */ repeatPrecedingCharacter(params?: number[]): boolean; - /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): boolean; - /** CSI d */ linePosAbsolute(params?: number[]): boolean; - /** CSI e */ vPositionRelative(params?: number[]): boolean; - /** CSI f */ hVPosition(params?: number[]): boolean; - /** CSI g */ tabClear(params?: number[]): boolean; - /** CSI h */ setMode(params?: number[], collect?: string): boolean; - /** CSI l */ resetMode(params?: number[], collect?: string): boolean; - /** CSI m */ charAttributes(params?: number[]): boolean; - /** CSI n */ deviceStatus(params?: number[], collect?: string): boolean; - /** CSI p */ softReset(params?: number[], collect?: string): boolean; - /** CSI q */ setCursorStyle(params?: number[], collect?: string): boolean; - /** CSI r */ setScrollRegion(params?: number[], collect?: string): boolean; - /** CSI s */ saveCursor(params?: number[]): boolean; - /** CSI u */ restoreCursor(params?: number[]): boolean; + /** CSI @ */ insertChars(params?: number[]): void; + /** CSI A */ cursorUp(params?: number[]): void; + /** CSI B */ cursorDown(params?: number[]): void; + /** CSI C */ cursorForward(params?: number[]): void; + /** CSI D */ cursorBackward(params?: number[]): void; + /** CSI E */ cursorNextLine(params?: number[]): void; + /** CSI F */ cursorPrecedingLine(params?: number[]): void; + /** CSI G */ cursorCharAbsolute(params?: number[]): void; + /** CSI H */ cursorPosition(params?: number[]): void; + /** CSI I */ cursorForwardTab(params?: number[]): void; + /** CSI J */ eraseInDisplay(params?: number[]): void; + /** CSI K */ eraseInLine(params?: number[]): void; + /** CSI L */ insertLines(params?: number[]): void; + /** CSI M */ deleteLines(params?: number[]): void; + /** CSI P */ deleteChars(params?: number[]): void; + /** CSI S */ scrollUp(params?: number[]): void; + /** CSI T */ scrollDown(params?: number[], collect?: string): void; + /** CSI X */ eraseChars(params?: number[]): void; + /** CSI Z */ cursorBackwardTab(params?: number[]): void; + /** CSI ` */ charPosAbsolute(params?: number[]): void; + /** CSI a */ hPositionRelative(params?: number[]): void; + /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; + /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void; + /** CSI d */ linePosAbsolute(params?: number[]): void; + /** CSI e */ vPositionRelative(params?: number[]): void; + /** CSI f */ hVPosition(params?: number[]): void; + /** CSI g */ tabClear(params?: number[]): void; + /** CSI h */ setMode(params?: number[], collect?: string): void; + /** CSI l */ resetMode(params?: number[], collect?: string): void; + /** CSI m */ charAttributes(params?: number[]): void; + /** CSI n */ deviceStatus(params?: number[], collect?: string): void; + /** CSI p */ softReset(params?: number[], collect?: string): void; + /** CSI q */ setCursorStyle(params?: number[], collect?: string): void; + /** CSI r */ setScrollRegion(params?: number[], collect?: string): void; + /** CSI s */ saveCursor(params?: number[]): void; + /** CSI u */ restoreCursor(params?: number[]): void; /** OSC 0 - OSC 2 */ setTitle(data: string): boolean; - /** ESC E */ nextLine(): boolean; - /** ESC = */ keypadApplicationMode(): boolean; - /** ESC > */ keypadNumericMode(): boolean; + OSC 2 */ setTitle(data: string): void; + /** ESC E */ nextLine(): void; + /** ESC = */ keypadApplicationMode(): void; + /** ESC > */ keypadNumericMode(): void; /** ESC % G - ESC % @ */ selectDefaultCharset(): boolean; + ESC % @ */ selectDefaultCharset(): void; /** ESC ( C ESC ) C ESC * C ESC + C ESC - C ESC . C - ESC / C */ selectCharset(collectAndFlag: string): boolean; - /** ESC D */ index(): boolean; - /** ESC H */ tabSet(): boolean; - /** ESC M */ reverseIndex(): boolean; - /** ESC c */ reset(): boolean; + ESC / C */ selectCharset(collectAndFlag: string): void; + /** ESC D */ index(): void; + /** ESC H */ tabSet(): void; + /** ESC M */ reverseIndex(): void; + /** ESC c */ reset(): void; /** ESC n ESC o ESC | ESC } - ESC ~ */ setgLevel(level: number): boolean; + ESC ~ */ setgLevel(level: number): void; } export interface ILinkMatcher { From 625e168e1be9149f01a6ab859c1c43a7cb6336de Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:02:11 -0400 Subject: [PATCH 35/83] Convert title and resize events --- src/Terminal.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cb0e30501..b346e8516 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -265,14 +265,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); - this.on('title', e => this._onTitleChange.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); this.on('refresh', e => this._onRender.fire(e)); - this.on('resize', e => this._onResize.fire(e)); // TODO: Remove these in v4 // Fire old style events from new emitters this.onKey(e => this.emit('key', e.key, e.domEvent)); + this.onResize(e => this.emit('resize', e)); + this.onTitleChange(e => this.emit('title', e)); } public dispose(): void { @@ -770,7 +770,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.viewport); this.register(this.addDisposableListener('cursormove', () => this.renderer.onCursorMove())); - this.register(this.addDisposableListener('resize', () => this.renderer.onResize(this.cols, this.rows))); + this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); this.register(this.addDisposableListener('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio))); @@ -1778,7 +1778,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } this.refresh(0, this.rows - 1); - this.emit('resize', {cols: x, rows: y}); + this._onResize.fire({ cols: x, rows: y }); } /** @@ -1859,13 +1859,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param title The title to populate in the event. */ public handleTitle(title: string): void { - /** - * This event is emitted when the title of the terminal is changed - * from inside the terminal. The parameter is the new title. - * - * @event title - */ - this.emit('title', title); + this._onTitleChange.fire(title); } /** From 6ff81c43f64eff514f009879f360fa7c277d997a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:09:31 -0400 Subject: [PATCH 36/83] Convert marker dispose, selection manager newselection --- src/Buffer.ts | 8 ++++++-- src/SelectionManager.ts | 12 ++++++++---- src/Terminal.ts | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index c1dc2fecd..e84908d05 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -10,6 +10,7 @@ import { IMarker } from 'xterm'; import { BufferLine, CellData, AttributeData } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -615,7 +616,7 @@ export class Buffer implements IBuffer { marker.line -= event.amount; } })); - marker.register(marker.addDisposableListener('dispose', () => this._removeMarker(marker))); + marker.register(marker.onDispose(() => this._removeMarker(marker))); return marker; } @@ -636,6 +637,9 @@ export class Marker extends EventEmitter implements IMarker { public get id(): number { return this._id; } + private _onDispose = new EventEmitter2(); + public get onDispose(): IEvent { return this._onDispose.event; } + constructor( public line: number ) { @@ -648,7 +652,7 @@ export class Marker extends EventEmitter implements IMarker { } this.isDisposed = true; // Emit before super.dispose such that dispose listeners get a change to react - this.emit('dispose'); + this._onDispose.fire(); super.dispose(); } } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 366dc767c..544994281 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -12,6 +12,7 @@ import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; import { IDisposable } from 'xterm'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -107,6 +108,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseDownTimeStamp: number; + private _onNewMouseSelection = new EventEmitter2(); + public get onNewMouseSelection(): IEvent { return this._onNewMouseSelection.event; } + constructor( private _terminal: ITerminal, private _charMeasure: CharMeasure @@ -244,10 +248,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager /** * Queues a refresh, redrawing the selection on the next opportunity. - * @param isNewSelection Whether the selection should be registered as a new + * @param isNewMouseSelection Whether the selection should be registered as a new * selection on Linux. */ - public refresh(isNewSelection?: boolean): void { + public refresh(isNewMouseSelection?: boolean): void { // Queue the refresh for the renderer if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh()); @@ -255,10 +259,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the platform is Linux and the refresh call comes from a mouse event, // we need to update the selection for middle click to paste selection. - if (Browser.isLinux && isNewSelection) { + if (Browser.isLinux && isNewMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this.emit('newselection', this.selectionText); + this._onNewMouseSelection.fire(this.selectionText); } } } diff --git a/src/Terminal.ts b/src/Terminal.ts index b346e8516..c92f5da2b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -783,7 +783,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); - this.register(this.selectionManager.addDisposableListener('newselection', text => { + this.register(this.selectionManager.onNewMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired // only on Linux to enable middle click to paste selection. From dee4429151c2789f9208977c737d09d6aa5fee38 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 11:11:16 -0400 Subject: [PATCH 37/83] Remove unneeded interfaces --- src/Types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index 7967662ac..6e0108aef 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -111,8 +111,6 @@ export interface ICompositionHelper { export interface IInputHandler { parse(data: string): void; print(data: Uint32Array, start: number, end: number): void; - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; /** C0 BEL */ bell(): void; /** C0 LF */ lineFeed(): void; From 8b5b64605e0c0168fb0c1b529eee52b0a9d74fea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:53:22 -0400 Subject: [PATCH 38/83] Start find previous from the current viewport --- src/addons/search/SearchHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 57a8c0a88..3dfc88911 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -115,8 +115,8 @@ export class SearchHelper implements ISearchHelper { } const isReverseSearch = true; - let startRow = this._terminal.rows - 1; - let startCol: number = this._terminal.cols; + let startRow = this._terminal._core.buffer.ydisp + this._terminal.rows - 1; + let startCol = this._terminal.cols; if (selectionManager.selectionStart) { // Start from the selection start if there is a selection From 8e5d372b81141cbe1af2c3ca842dec52ae16ed4a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:31:59 -0700 Subject: [PATCH 39/83] Convert cursormove to EventEmitter2 --- src/InputHandler.ts | 6 +++++- src/Terminal.test.ts | 10 ++++++++++ src/Terminal.ts | 6 ++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 95eab4a33..d04712865 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,6 +15,7 @@ import { Disposable } from './common/Lifecycle'; import { concat } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; import { CellData, Attributes, FgFlags, BgFlags, AttributeData } from './BufferLine'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * Map collect to glevel. Used in `selectCharset`. @@ -106,6 +107,9 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _workCell: CellData = new CellData(); + private _onCursorMove = new EventEmitter2(); + public get onCursorMove(): IEvent { return this._onCursorMove.event; } + constructor( protected _terminal: IInputHandlingTerminal, private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) @@ -305,7 +309,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer = this._terminal.buffer; if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { - this._terminal.emit('cursormove'); + this._onCursorMove.fire(); } } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f4b717d8b..2422c038b 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -85,6 +85,16 @@ describe('term.js addons', () => { }); }); + describe('cursormove', () => { + it('should emit a cursormove event', (done) => { + term.on('cursormove', () => { + done(); + }); + + term.write('foo'); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index c92f5da2b..64178c744 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('cursormove', () => this._onCursorMove.fire()); this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); @@ -270,6 +269,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Remove these in v4 // Fire old style events from new emitters + this.onCursorMove(() => this.emit('cursormove')); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onResize(e => this.emit('resize', e)); this.onTitleChange(e => this.emit('title', e)); @@ -350,7 +350,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); + this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this.register(this._inputHandler); + // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -769,7 +771,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.viewport.onThemeChanged(this.renderer.colorManager.colors); this.register(this.viewport); - this.register(this.addDisposableListener('cursormove', () => this.renderer.onCursorMove())); + this.register(this.onCursorMove(() => this.renderer.onCursorMove())); this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); From f111aa27dc5a076402ee4d82153343c11c07b615 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:35:41 -0700 Subject: [PATCH 40/83] Convert linefeed to EventEmitter2 --- src/AccessibilityManager.ts | 2 +- src/InputHandler.ts | 10 ++++------ src/Terminal.test.ts | 12 +++++++++++- src/Terminal.ts | 4 +++- src/WindowsMode.ts | 2 +- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a8e0ba1bc..866dc3c7d 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -77,7 +77,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); - this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); + this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); this.register(this._terminal.onKey(e => this._onKey(e.key))); this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d04712865..da167e267 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -109,6 +109,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } constructor( protected _terminal: IInputHandlingTerminal, @@ -458,12 +460,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x >= this._terminal.cols) { buffer.x--; } - /** - * This event is emitted whenever the terminal outputs a LF or NL. - * - * @event linefeed - */ - this._terminal.emit('linefeed'); + + this._onLineFeed.fire(); } /** diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2422c038b..b44baa5be 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -17,7 +17,7 @@ class TestTerminal extends Terminal { public keyPress(ev: any): boolean { return this._keyPress(ev); } } -describe('term.js addons', () => { +describe('xterm.js', () => { let term: TestTerminal; const termOptions = { cols: INIT_COLS, @@ -95,6 +95,16 @@ describe('term.js addons', () => { }); }); + describe('linefeed', () => { + it('should emit a linefeed event', (done) => { + term.on('linefeed', () => { + done(); + }); + + term.write('\n'); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index 64178c744..662ce7eb8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); @@ -271,6 +270,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); this.onKey(e => this.emit('key', e.key, e.domEvent)); + this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -349,8 +349,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this._writeStopped = false; this._userScrolling = false; + // Register input handler and refire/handle events this._inputHandler = new InputHandler(this); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); + this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); // Reuse renderer if the Terminal is being recreated via a reset call. diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts index 33a9bed5e..ac1d193e4 100644 --- a/src/WindowsMode.ts +++ b/src/WindowsMode.ts @@ -18,7 +18,7 @@ export function applyWindowsMode(terminal: ITerminal): IDisposable { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - return terminal.addDisposableListener('linefeed', () => { + return terminal.onLineFeed(() => { const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); const lastChar = line.get(terminal.cols - 1); From 2c96c262f4a520b6e7c967d01a961670ccf2dc7d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:42:31 -0700 Subject: [PATCH 41/83] Convert scroll to EventEmitter2 --- src/AccessibilityManager.ts | 2 +- src/InputHandler.ts | 4 +++- src/Terminal.test.ts | 18 ++++++++++++++++-- src/Terminal.ts | 18 ++++++------------ 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 866dc3c7d..aeba5d8be 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -74,7 +74,7 @@ export class AccessibilityManager extends Disposable { this.register(this._renderRowsDebouncer); this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); - this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); + this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index da167e267..c77b1c602 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -111,6 +111,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } constructor( protected _terminal: IInputHandlingTerminal, @@ -764,7 +766,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this._terminal.emit('scroll', 0); + this._onScroll.fire(0); } break; } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b44baa5be..b48183478 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -90,7 +90,6 @@ describe('xterm.js', () => { term.on('cursormove', () => { done(); }); - term.write('foo'); }); }); @@ -100,11 +99,26 @@ describe('xterm.js', () => { term.on('linefeed', () => { done(); }); - term.write('\n'); }); }); + describe('scroll', () => { + it('should emit a scroll event when scrollback is created', (done) => { + term.on('scroll', () => { + done(); + }); + term.write('\n'.repeat(INIT_ROWS)); + }); + it('should emit a scroll event when scrollback is cleared', (done) => { + term.write('\n'.repeat(INIT_ROWS)); + term.on('scroll', () => { + done(); + }); + term.clear(); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index 662ce7eb8..dfdb85693 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -263,7 +263,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Replace EventEmitter with EventEmitter2 internally this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); - this.on('scroll', e => this._onScroll.fire(e)); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 @@ -272,6 +271,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); + this.onScroll(e => this.emit('scroll', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -740,7 +740,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._mouseZoneManager = new MouseZoneManager(this); this.register(this._mouseZoneManager); - this.register(this.addDisposableListener('scroll', () => this._mouseZoneManager.clearAll())); + this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); this.linkifier.attachToDom(this._mouseZoneManager); this.textarea = document.createElement('textarea'); @@ -795,7 +795,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.textarea.focus(); this.textarea.select(); })); - this.register(this.addDisposableListener('scroll', () => { + this.register(this.onScroll(() => { this.viewport.syncScrollArea(); this.selectionManager.refresh(); })); @@ -1302,13 +1302,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); - /** - * This event is emitted whenever the terminal is scrolled. - * The one parameter passed is the new y display position. - * - * @event scroll - */ - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } /** @@ -1337,7 +1331,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } if (!suppressScrollEvent) { - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } this.refresh(0, this.rows - 1); @@ -1825,7 +1819,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } /** From 87328fe29365a5b9fc2a64232c3e857eb4a01923 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:53:02 -0700 Subject: [PATCH 42/83] Convert data to EventEmitter2 --- src/InputHandler.ts | 8 +++++--- src/Terminal.ts | 28 ++++++++++++++-------------- src/Types.ts | 4 ---- src/addons/attach/Interfaces.ts | 1 + src/addons/attach/attach.ts | 6 ++++-- src/addons/terminado/Interfaces.ts | 7 ++++++- src/addons/terminado/terminado.ts | 7 ++++--- src/public/Terminal.ts | 2 +- src/ui/TestUtils.test.ts | 2 +- typings/xterm.d.ts | 4 ++-- 10 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c77b1c602..fe8ab53d1 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -109,6 +109,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } + private _onData = new EventEmitter2(); + public get onData(): IEvent { return this._onData.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter2(); @@ -1725,13 +1727,13 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params[0]) { case 5: // status report - this._terminal.emit('data', `${C0.ESC}[0n`); + this._onData.fire(`${C0.ESC}[0n`); break; case 6: // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._terminal.emit('data', `${C0.ESC}[${y};${x}R`); + this._onData.fire(`${C0.ESC}[${y};${x}R`); break; } } else if (collect === '?') { @@ -1742,7 +1744,7 @@ export class InputHandler extends Disposable implements IInputHandler { // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._terminal.emit('data', `${C0.ESC}[?${y};${x}R`); + this._onData.fire(`${C0.ESC}[?${y};${x}R`); break; case 15: // no printer diff --git a/src/Terminal.ts b/src/Terminal.ts index dfdb85693..a52da3d5a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -224,22 +224,22 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onLineFeed = new EventEmitter2(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } - private _onSelectionChange = new EventEmitter2(); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } - private _onInput = new EventEmitter2(); - public get onInput(): IEvent { return this._onInput.event; } - private _onTitleChange = new EventEmitter2(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } - private _onScroll = new EventEmitter2(); - public get onScroll(): IEvent { return this._onScroll.event; } + private _onData = new EventEmitter2(); + public get onData(): IEvent { return this._onData.event; } private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onResize = new EventEmitter2<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + private _onTitleChange = new EventEmitter2(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } /** * Creates a new `Terminal` object. @@ -262,12 +262,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Replace EventEmitter with EventEmitter2 internally this.on('selection', () => this._onSelectionChange.fire()); - this.on('data', e => this._onInput.fire(e)); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); + this.onData(e => this.emit('data', e)); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); @@ -313,7 +313,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.rows = Math.max(this.options.rows, MINIMUM_ROWS); if (this.options.handler) { - this.on('data', this.options.handler); + this.onData(this.options.handler); } this.cursorState = 0; @@ -1831,7 +1831,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } /** - * Emit the 'data' event and populate the given data. + * Emit the data event and populate the given data. * @param data The data to populate in the event. */ public handler(data: string): void { @@ -1849,7 +1849,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } - this.emit('data', data); + this._onData.fire(data); } /** diff --git a/src/Types.ts b/src/Types.ts index 0fcf291ed..ef99f147d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -221,10 +221,6 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce bracketedPasteMode: boolean; applicationCursor: boolean; - /** - * Emit the 'data' event and populate the given data. - * @param data The data to populate in the event. - */ handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts index ab5846f5e..4b2690993 100644 --- a/src/addons/attach/Interfaces.ts +++ b/src/addons/attach/Interfaces.ts @@ -14,6 +14,7 @@ export interface IAttachAddonTerminal extends Terminal { __socket?: WebSocket; __attachSocketBuffer?: string; + __dataListener?: IDisposable; __getMessage?(ev: MessageEvent): void; __flushBuffer?(): void; diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index f121e2e2d..2c8a5d4d2 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -90,7 +90,8 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage)); if (bidirectional) { - addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData)); + addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.__dataListener); } addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket))); @@ -119,7 +120,8 @@ function addSocketListener(socket: WebSocket, type: string, handler: (this: WebS */ export function detach(term: Terminal, socket: WebSocket): void { const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); + addonTerminal.__dataListener.dispose(); + addonTerminal.__dataListener = undefined; socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; diff --git a/src/addons/terminado/Interfaces.ts b/src/addons/terminado/Interfaces.ts index 8f17b0cc7..dd7b045ca 100644 --- a/src/addons/terminado/Interfaces.ts +++ b/src/addons/terminado/Interfaces.ts @@ -5,11 +5,16 @@ * Implements the attach method, that attaches the terminal to a WebSocket stream. */ -import { Terminal } from 'xterm'; +import { Terminal, IDisposable } from 'xterm'; export interface ITerminadoAddonTerminal extends Terminal { + _core: { + register(d: T): void; + }; + __socket?: WebSocket; __attachSocketBuffer?: string; + __dataListener?: IDisposable; __getMessage?(ev: MessageEvent): void; __flushBuffer?(): void; diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index b36c2cbfa..cefa8087f 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,9 +59,9 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal.onInput(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData); } - addonTerminal.onResize(addonTerminal.__setSize); + addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize)); socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket)); socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket)); @@ -75,7 +75,8 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional */ export function terminadoDetach(term: Terminal, socket: WebSocket): void { const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); + addonTerminal.__dataListener.dispose(); + addonTerminal.__dataListener = undefined; socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 53c3c693f..d05a4f100 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -19,7 +19,7 @@ export class Terminal implements ITerminalApi { public get onCursorMove(): IEvent { return this._core.onCursorMove; } public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } - public get onInput(): IEvent { return this._core.onInput; } + public get onData(): IEvent { return this._core.onData; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } public get onScroll(): IEvent { return this._core.onScroll; } public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ddf8e2c81..ae57d1f94 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -23,7 +23,7 @@ export class MockTerminal implements ITerminal { onCursorMove: IEvent; onLineFeed: IEvent; onSelectionChange: IEvent; - onInput: IEvent; + onData: IEvent; onTitleChange: IEvent; onScroll: IEvent; onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f8bbdbdb6..2c4bb2ec2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -392,13 +392,13 @@ declare module 'xterm' { onSelectionChange: IEvent; /** - * Adds an event listener for when an input event fires. This happens for + * Adds an event listener for when a data event fires. This happens for * example when the user types or pastes into the terminal. The event value * is whatever `string` results, in a typical setup, this should be passed * on to the backing pty. * @returns an `IDisposable` to stop listening. */ - onInput: IEvent; + onData: IEvent; /** * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. From db78b75e8bd6a88b870daa5b1fa35a990afef476 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:55:33 -0700 Subject: [PATCH 43/83] Convert selection to EventEmitter2 --- src/SelectionManager.ts | 14 ++++++++------ src/Terminal.ts | 5 +++-- src/addons/terminado/terminado.ts | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 544994281..087e8995d 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -108,8 +108,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseDownTimeStamp: number; - private _onNewMouseSelection = new EventEmitter2(); - public get onNewMouseSelection(): IEvent { return this._onNewMouseSelection.event; } + private _onLinuxMouseSelection = new EventEmitter2(); + public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } constructor( private _terminal: ITerminal, @@ -262,7 +264,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager if (Browser.isLinux && isNewMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this._onNewMouseSelection.fire(this.selectionText); + this._onLinuxMouseSelection.fire(this.selectionText); } } } @@ -322,7 +324,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -332,7 +334,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._terminal.cols, end]; this.refresh(); - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } /** @@ -650,7 +652,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { (new AltClickHandler(event, this._terminal)).move(); } else if (this.hasSelection) { - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index a52da3d5a..59f675f3a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('selection', () => this._onSelectionChange.fire()); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 @@ -271,6 +270,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); + this.onSelectionChange(() => this.emit('selection')); this.onScroll(e => this.emit('scroll', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -785,9 +785,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); + this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); - this.register(this.selectionManager.onNewMouseSelection(text => { + this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired // only on Linux to enable middle click to paste selection. diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index cefa8087f..9895a07b8 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,7 +59,7 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData)); } addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize)); From 290bde616331026e1a591b341b4da249b239d0f0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:16:26 -0700 Subject: [PATCH 44/83] Convert refresh to EventEmitter2 --- src/Terminal.ts | 9 ++++----- src/renderer/Renderer.ts | 6 +++++- src/renderer/Types.ts | 3 +++ src/renderer/dom/DomRenderer.ts | 6 +++++- src/ui/TestUtils.test.ts | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 59f675f3a..4a6673873 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -260,15 +260,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.options = clone(options); this._setup(); - // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('refresh', e => this._onRender.fire(e)); - // TODO: Remove these in v4 // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); this.onData(e => this.emit('data', e)); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); + this.onRender(e => this.emit('refresh', e)); this.onResize(e => this.emit('resize', e)); this.onSelectionChange(() => this.emit('selection')); this.onScroll(e => this.emit('scroll', e)); @@ -687,8 +685,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(addDisposableDomListener(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart())); this.register(addDisposableDomListener(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e))); this.register(addDisposableDomListener(this.textarea, 'compositionend', () => this._compositionHelper.compositionend())); - this.register(this.addDisposableListener('refresh', () => this._compositionHelper.updateCompositionElements())); - this.register(this.addDisposableListener('refresh', (data) => this._queueLinkification(data.start, data.end))); + this.register(this.onRender(() => this._compositionHelper.updateCompositionElements())); + this.register(this.onRender(e => this._queueLinkification(e.start, e.end))); } /** @@ -838,6 +836,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } + this.renderer.onRender(e => this._onRender.fire(e)); this.register(this.renderer); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2c1b516ae..b7ad68543 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -15,6 +15,7 @@ import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { ITheme } from 'xterm'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; +import { EventEmitter2, IEvent } from '../common/EventEmitter2'; export class Renderer extends EventEmitter implements IRenderer { private _renderDebouncer: RenderDebouncer; @@ -29,6 +30,9 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor(private _terminal: ITerminal, theme: ITheme) { super(); const allowTransparency = this._terminal.options.allowTransparency; @@ -197,7 +201,7 @@ export class Renderer extends EventEmitter implements IRenderer { */ private _renderRows(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); - this._terminal.emit('refresh', { start, end }); + this._onRender.fire({ start, end }); } /** diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index f2271f95c..71e5fc307 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,6 +5,7 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { IEventEmitter, ITheme, IDisposable } from 'xterm'; +import { IEvent } from '../common/EventEmitter2'; /** * Flags used to render terminal text properly. @@ -27,6 +28,8 @@ export interface IRenderer extends IEventEmitter, IDisposable { dimensions: IRenderDimensions; colorManager: IColorManager; + onRender: IEvent<{ start: number, end: number }>; + dispose(): void; setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 78ccc620e..0f70e93b1 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -11,6 +11,7 @@ import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -43,6 +44,9 @@ export class DomRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor(private _terminal: ITerminal, theme: ITheme | undefined) { super(); const allowTransparency = this._terminal.options.allowTransparency; @@ -350,7 +354,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); } - this._terminal.emit('refresh', {start, end}); + this._onRender.fire({ start, end }); } private get _terminalSelector(): string { diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ae57d1f94..9488a9272 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -354,6 +354,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + onRender: IEvent<{ start: number; end: number; }>; dispose(): void { throw new Error('Method not implemented.'); } From 2e0e24c382f623dc76759ce86a25395ec106769e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:26:18 -0700 Subject: [PATCH 45/83] Remove extends EventEmitter from SelectinoManager --- src/AccessibilityManager.ts | 4 ++-- src/SelectionManager.ts | 21 ++++++++++----------- src/Terminal.ts | 2 +- src/Types.ts | 6 ++++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index aeba5d8be..d72f6647e 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -72,8 +72,8 @@ export class AccessibilityManager extends Disposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this.register(this._renderRowsDebouncer); - this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); - this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); + this.register(this._terminal.onResize(e => this._onResize(e.rows))); + this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end))); this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 087e8995d..eed206cc6 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, IBufferLine, ISelectionRedrawRequestEvent } from './Types'; import { MouseHelper } from './ui/MouseHelper'; import * as Browser from './common/Platform'; import { CharMeasure } from './ui/CharMeasure'; -import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; @@ -68,10 +67,10 @@ export const enum SelectionMode { * SelectionModel, SelectionManager handles with all logic associated with * dealing with the selection, including handling mouse interaction, wide * characters and fetching the actual text within the selection. Rendering is - * not handled by the SelectionManager but a 'refresh' event is fired when the - * selection is ready to be redrawn. + * not handled by the SelectionManager but the onRedrawRequest event is fired + * when the selection is ready to be redrawn (on an animation frame). */ -export class SelectionManager extends EventEmitter implements ISelectionManager { +export class SelectionManager implements ISelectionManager { protected _model: SelectionModel; /** @@ -110,6 +109,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _onLinuxMouseSelection = new EventEmitter2(); public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + private _onRedrawRequest = new EventEmitter2(); + public get onRedrawRequest(): IEvent { return this._onRedrawRequest.event; } private _onSelectionChange = new EventEmitter2(); public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } @@ -117,7 +118,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _terminal: ITerminal, private _charMeasure: CharMeasure ) { - super(); this._initListeners(); this.enable(); @@ -126,7 +126,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } public dispose(): void { - super.dispose(); this._removeMouseDownListeners(); } @@ -250,10 +249,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager /** * Queues a refresh, redrawing the selection on the next opportunity. - * @param isNewMouseSelection Whether the selection should be registered as a new + * @param isLinuxMouseSelection Whether the selection should be registered as a new * selection on Linux. */ - public refresh(isNewMouseSelection?: boolean): void { + public refresh(isLinuxMouseSelection?: boolean): void { // Queue the refresh for the renderer if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh()); @@ -261,7 +260,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the platform is Linux and the refresh call comes from a mouse event, // we need to update the selection for middle click to paste selection. - if (Browser.isLinux && isNewMouseSelection) { + if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { this._onLinuxMouseSelection.fire(this.selectionText); @@ -275,7 +274,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager */ private _refresh(): void { this._refreshAnimationFrame = null; - this.emit('refresh', { + this._onRedrawRequest.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN diff --git a/src/Terminal.ts b/src/Terminal.ts index 4a6673873..cb2743fd4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -785,7 +785,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); - this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); + this.register(this.selectionManager.onRedrawRequest(e => this.renderer.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired diff --git a/src/Types.ts b/src/Types.ts index ef99f147d..be75b0c9b 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -324,6 +324,12 @@ export interface ISelectionManager { selectWordAtCursor(event: MouseEvent): void; } +export interface ISelectionRedrawRequestEvent { + start: [number, number]; + end: [number, number]; + columnSelectMode: boolean; +} + export interface ILinkifier extends IEventEmitter { attachToDom(mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; From 36567e60df8220374b58d538b63a582cb8279502 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:36:08 -0700 Subject: [PATCH 46/83] Remove extends EventEmitter from more classes --- src/AccessibilityManager.ts | 2 +- src/Buffer.ts | 5 ++--- src/Terminal.ts | 2 +- src/renderer/Renderer.ts | 8 +++++--- src/renderer/Types.ts | 5 +++-- src/renderer/dom/DomRenderer.ts | 10 ++++++++-- src/ui/TestUtils.test.ts | 1 + 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index d72f6647e..e2115797f 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -85,7 +85,7 @@ export class AccessibilityManager extends Disposable { // should be listened to instead? That would mean that the order of events are always // guarenteed this.register(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions())); - this.register(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions())); + this.register(this._terminal.renderer.onCanvasResize(() => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the dprchange event isn't supported this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); diff --git a/src/Buffer.ts b/src/Buffer.ts index ba686238e..c1c08c85a 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -5,12 +5,12 @@ import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData, IAttributeData } from './Types'; -import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; import { BufferLine, CellData, AttributeData } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; +import { Disposable } from '../lib/common/Lifecycle'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -629,7 +629,7 @@ export class Buffer implements IBuffer { } } -export class Marker extends EventEmitter implements IMarker { +export class Marker extends Disposable implements IMarker { private static _nextId = 1; private _id: number = Marker._nextId++; @@ -653,7 +653,6 @@ export class Marker extends EventEmitter implements IMarker { this.isDisposed = true; // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); - super.dispose(); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index cb2743fd4..16ac69b32 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -780,7 +780,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); - this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); + this.register(this.renderer.onCanvasResize(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b7ad68543..2a205be36 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -10,14 +10,14 @@ import { ColorManager } from './ColorManager'; import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; -import { EventEmitter } from '../common/EventEmitter'; import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { ITheme } from 'xterm'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; +import { Disposable } from '../common/Lifecycle'; -export class Renderer extends EventEmitter implements IRenderer { +export class Renderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; @@ -30,6 +30,8 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } @@ -138,7 +140,7 @@ export class Renderer extends EventEmitter implements IRenderer { this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; - this.emit('resize', { + this._onCanvasResize.fire({ width: this.dimensions.canvasWidth, height: this.dimensions.canvasHeight }); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 71e5fc307..9d2ddcc32 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -4,7 +4,7 @@ */ import { ITerminal, CharacterJoinerHandler } from '../Types'; -import { IEventEmitter, ITheme, IDisposable } from 'xterm'; +import { ITheme, IDisposable } from 'xterm'; import { IEvent } from '../common/EventEmitter2'; /** @@ -24,10 +24,11 @@ export const enum FLAGS { * Note that IRenderer implementations should emit the refresh event after * rendering rows to the screen. */ -export interface IRenderer extends IEventEmitter, IDisposable { +export interface IRenderer extends IDisposable { dimensions: IRenderDimensions; colorManager: IColorManager; + onCanvasResize: IEvent<{ width: number, height: number }>; onRender: IEvent<{ start: number, end: number }>; dispose(): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 0f70e93b1..c13b6e278 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -6,12 +6,12 @@ import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types'; import { ITheme } from 'xterm'; -import { EventEmitter } from '../../common/EventEmitter'; import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; +import { Disposable } from '../../common/Lifecycle'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -30,7 +30,7 @@ let nextTerminalId = 1; * particularly fast or feature complete, more just stable and usable for when * canvas is not an option. */ -export class DomRenderer extends EventEmitter implements IRenderer { +export class DomRenderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; @@ -44,6 +44,8 @@ export class DomRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } @@ -242,6 +244,10 @@ export class DomRenderer extends EventEmitter implements IRenderer { public onResize(cols: number, rows: number): void { this._refreshRowElements(cols, rows); this._updateDimensions(); + this._onCanvasResize.fire({ + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); } public onCharSizeChanged(): void { diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9488a9272..5ee0f9d5c 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -354,6 +354,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + onCanvasResize: IEvent<{ width: number; height: number; }>; onRender: IEvent<{ start: number; end: number; }>; dispose(): void { throw new Error('Method not implemented.'); From f4c7e3fd7dbeede76668eabe212bf261afc67a79 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:43:42 -0700 Subject: [PATCH 47/83] Remove extends EventEmitter from Linkifier --- src/Linkifier.ts | 24 +++++++++++++++--------- src/Types.ts | 14 ++++++-------- src/renderer/LinkRenderLayer.ts | 12 ++++++------ src/renderer/dom/DomRenderer.ts | 10 +++++----- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 80399904c..8c57e1b22 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -4,15 +4,15 @@ */ import { IMouseZoneManager } from './ui/Types'; -import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; -import { EventEmitter } from './common/EventEmitter'; import { getStringCellWidth } from './CharWidth'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The Linkifier applies links to rows shortly after they have been refreshed. */ -export class Linkifier extends EventEmitter implements ILinkifier { +export class Linkifier implements ILinkifier { /** * The time to wait after a row is changed before it is linkified. This prevents * the costly operation of searching every row multiple times, potentially a @@ -34,10 +34,16 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; + private _onLinkHover = new EventEmitter2(); + public get onLinkHover(): IEvent { return this._onLinkHover.event; } + private _onLinkLeave = new EventEmitter2(); + public get onLinkLeave(): IEvent { return this._onLinkLeave.event; } + private _onLinkTooltip = new EventEmitter2(); + public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } + constructor( protected _terminal: ITerminal ) { - super(); this._rowsToLinkify = { start: null, end: null @@ -283,18 +289,18 @@ export class Linkifier extends EventEmitter implements ILinkifier { } window.open(uri, '_blank'); }, - e => { - this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + () => { + this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.add('xterm-cursor-pointer'); }, e => { - this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); if (matcher.hoverTooltipCallback) { matcher.hoverTooltipCallback(e, uri); } }, () => { - this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); @@ -309,7 +315,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { )); } - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkHoverEvent { + private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent { return { x1, y1, x2, y2, cols: this._terminal.cols, fg }; } } diff --git a/src/Types.ts b/src/Types.ts index be75b0c9b..a70dfdad2 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -23,12 +23,6 @@ export type CharacterJoinerHandler = (text: string) => [number, number][]; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; -export const enum LinkHoverEventTypes { - HOVER = 'linkhover', - TOOLTIP = 'linktooltip', - LEAVE = 'linkleave' -} - /** * This interface encapsulates everything needed from the Terminal by the * InputHandler. This cleanly separates the large amount of methods needed by @@ -194,7 +188,7 @@ export interface ILinkMatcher { willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } -export interface ILinkHoverEvent { +export interface ILinkifierEvent { x1: number; y1: number; x2: number; @@ -330,7 +324,11 @@ export interface ISelectionRedrawRequestEvent { columnSelectMode: boolean; } -export interface ILinkifier extends IEventEmitter { +export interface ILinkifier { + onLinkHover: IEvent; + onLinkLeave: IEvent; + onLinkTooltip: IEvent; + attachToDom(mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 855830e40..abb899b0f 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; +import { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; export class LinkRenderLayer extends BaseRenderLayer { - private _state: ILinkHoverEvent = null; + private _state: ILinkifierEvent = null; constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e)); - terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); + terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } public resize(terminal: ITerminal, dim: IRenderDimensions): void { @@ -40,7 +40,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onLinkHover(e: ILinkHoverEvent): void { + private _onLinkHover(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (is256Color(e.fg)) { @@ -64,7 +64,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onLinkLeave(e: ILinkHoverEvent): void { + private _onLinkLeave(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index c13b6e278..1d879fcd1 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; -import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types'; +import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; import { ITheme } from 'xterm'; import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; @@ -87,8 +87,8 @@ export class DomRenderer extends Disposable implements IRenderer { this._terminal.screenElement.appendChild(this._rowContainer); this._terminal.screenElement.appendChild(this._selectionContainer); - this._terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e)); - this._terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); + this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } public dispose(): void { @@ -370,11 +370,11 @@ export class DomRenderer extends Disposable implements IRenderer { public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } public deregisterCharacterJoiner(joinerId: number): boolean { return false; } - private _onLinkHover(e: ILinkHoverEvent): void { + private _onLinkHover(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); } - private _onLinkLeave(e: ILinkHoverEvent): void { + private _onLinkLeave(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); } From b9bc219eccb1201acd76d0c3cb8b037a7245e133 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:52:18 -0700 Subject: [PATCH 48/83] Make events in d.ts alphabetical --- typings/xterm.d.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2c4bb2ec2..24a30b256 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -379,18 +379,6 @@ declare module 'xterm' { */ onCursorMove: IEvent; - /** - * Adds an event listener for when a line feed is added. - * @returns an `IDisposable` to stop listening. - */ - onLineFeed: IEvent; - - /** - * Adds an event listener for when a selection change occurs. - * @returns an `IDisposable` to stop listening. - */ - onSelectionChange: IEvent; - /** * Adds an event listener for when a data event fires. This happens for * example when the user types or pastes into the terminal. The event value @@ -401,11 +389,18 @@ declare module 'xterm' { onData: IEvent; /** - * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. - * The event value is the new title. + * Adds an event listener for a key is pressed. The event value contains the + * string that will be sent in the data event as well as the DOM event that + * triggered it. * @returns an `IDisposable` to stop listening. */ - onTitleChange: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; /** * Adds an event listener for when a scroll occurs. The event value is the @@ -415,12 +410,10 @@ declare module 'xterm' { onScroll: IEvent; /** - * Adds an event listener for a key is pressed. The event value contains the - * string that will be sent in the data event as well as the DOM event that - * triggered it. + * Adds an event listener for when a selection change occurs. * @returns an `IDisposable` to stop listening. */ - onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onSelectionChange: IEvent; /** * Adds an event listener for when rows are rendered. The event value @@ -437,6 +430,13 @@ declare module 'xterm' { */ onResize: IEvent<{ cols: number, rows: number }>; + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + /** * Unfocus the terminal. */ From cf4b59567bbd19852155a7ebee5188dd30763537 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 21:01:55 -0700 Subject: [PATCH 49/83] Add tests for new events --- src/Terminal.test.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b48183478..dd8e32e18 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -68,6 +68,81 @@ describe('xterm.js', () => { }); }); + describe('events', () => { + it('should fire the onData evnet', (done) => { + term.onData(() => done()); + term.handler('fake'); + }); + it('should fire the onCursorMove event', (done) => { + term.on('cursormove', () => done()); + term.write('foo'); + }); + it('should fire the onLineFeed event', (done) => { + term.on('linefeed', () => done()); + term.write('\n'); + }); + it('should fire a scroll event when scrollback is created', (done) => { + term.on('scroll', () => done()); + term.write('\n'.repeat(INIT_ROWS)); + }); + it('should fire a scroll event when scrollback is cleared', (done) => { + term.write('\n'.repeat(INIT_ROWS)); + term.on('scroll', () => done()); + term.clear(); + }); + it('should fire a key event after a keypress DOM event', (done) => { + term.onKey(e => { + assert.equal(typeof e.key, 'string'); + expect(e.domEvent).to.be.an.instanceof(Object); + done(); + }); + const evKeyPress = { + preventDefault: () => { }, + stopPropagation: () => { }, + type: 'keypress', + keyCode: 13 + }; + term.keyPress(evKeyPress); + }); + it('should fire a key event after a keydown DOM event', (done) => { + term.onKey(e => { + assert.equal(typeof e.key, 'string'); + expect(e.domEvent).to.be.an.instanceof(Object); + done(); + }); + const evKeyDown = { + preventDefault: () => { }, + stopPropagation: () => { }, + type: 'keydown', + keyCode: 13 + }; + term.keyDown(evKeyDown); + }); + it('should fire the onResize event', (done) => { + term.onResize(e => { + expect(e).to.have.keys(['cols', 'rows']); + assert.equal(typeof e.cols, 'number'); + assert.equal(typeof e.rows, 'number'); + done(); + }); + term.resize(1, 1); + }); + it('should fire the onScroll event', (done) => { + term.onScroll(e => { + assert.equal(typeof e, 'number'); + done(); + }); + term.scroll(); + }); + it('should fire the onTitleChange event', (done) => { + term.onTitleChange(e => { + assert.equal(e, 'title'); + done(); + }); + term.handleTitle('title'); + }); + }); + describe('on', () => { beforeEach(() => { term.on('key', () => { }); From a0ee09a7bc1f664ae9e45f6961081a91c26e1812 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Apr 2019 08:14:18 -0700 Subject: [PATCH 50/83] Add vscode setting for single quote style This will alwayts use single quotes when TS server does auto imports, implement interface, etc. --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..3c8a2dc2e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.preferences.quoteStyle": "single" +} From 8718d3a4ca5d3db507438bcc13659fa22e5361c9 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sat, 13 Apr 2019 21:12:59 -0400 Subject: [PATCH 51/83] feat: add underline support to DOM renderer part of #1896 --- src/renderer/dom/DomRendererRowFactory.test.ts | 10 ++++++++++ src/renderer/dom/DomRendererRowFactory.ts | 5 +++++ src/xterm.css | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 4402943c7..076f5d6a3 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -100,6 +100,16 @@ describe('DomRendererRowFactory', () => { ); }); + it('should add class for underline', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add classes for 256 foreground colors', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P256; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index a6cbeb786..60e2d5098 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -11,6 +11,7 @@ import { CellData, AttributeData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; export const ITALIC_CLASS = 'xterm-italic'; +export const UNDERLINE_CLASS = 'xterm-underline'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; @@ -88,6 +89,10 @@ export class DomRendererRowFactory { charElement.classList.add(DIM_CLASS); } + if (this._workCell.isUnderline()) { + charElement.classList.add(UNDERLINE_CLASS); + } + charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; const swapColor = this._workCell.isInverse(); diff --git a/src/xterm.css b/src/xterm.css index e80c25243..5448b5a5a 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -165,3 +165,7 @@ .xterm-dim { opacity: 0.5; } + +.xterm-underline { + text-decoration: underline; +} From 736bd53e49aed52418aff2ed61cda514fef650cc Mon Sep 17 00:00:00 2001 From: Bradley Bain Date: Thu, 25 Apr 2019 21:24:07 -0700 Subject: [PATCH 52/83] Clear line cache on terminal resize --- src/addons/search/SearchHelper.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 549a84496..7db1ed433 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -21,6 +21,7 @@ export class SearchHelper implements ISearchHelper { private _linesCache: string[] = null; private _linesCacheTimeoutId = 0; private _cursorMoveListener: IDisposable | undefined; + private _resizeListener: IDisposable | undefined; constructor(private _terminal: ISearchAddonTerminal) { this._destroyLinesCache = this._destroyLinesCache.bind(this); @@ -185,6 +186,7 @@ export class SearchHelper implements ISearchHelper { if (!this._linesCache) { this._linesCache = new Array(this._terminal._core.buffer.length); this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); + this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache()); } window.clearTimeout(this._linesCacheTimeoutId); @@ -197,6 +199,10 @@ export class SearchHelper implements ISearchHelper { 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; From 70db48b533a6e62b52500d664961e445613c88a0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 00:33:20 +0000 Subject: [PATCH 53/83] Add notice about beta builds in readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1fa8ad41e..b2e9f02f2 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,16 @@ Xterm.js follows a monthly release cycle roughly. All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). +### Beta builds + +Our CI releases beta builds to npm for every change that goes into master, install the latest beta build with: + +``` +npm install -S xterm@beta +``` + +These should generally be stable but some bugs may slip in, we recommend using the beta build primarily to test out new features and for verifying bug fixes. + ## Contributing You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development. From 65eaea39d3689cd58881e556c63c010ba5dbab91 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 00:45:51 +0000 Subject: [PATCH 54/83] Base the beta build off of the next minor version --- bin/publish.js | 6 ++++-- package.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 700752b48..ec7bf173d 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -32,13 +32,15 @@ function getNextVersion(tag) { console.error('The package.json version must be of the form x.y.z'); process.exit(1); } - const publishedVersions = getPublishedVersions(packageJson.version, tag); + const stableVersion = packageJson.version.split('.'); + const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; + const publishedVersions = getPublishedVersions(nextStableVersion, tag); if (publishedVersions.length === 0) { return `${packageJson.version}-${tag}1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => b.localeCompare(a))[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); - return `${packageJson.version}-${tag}${latestTagVersion + 1}`; + return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; } function getPublishedVersions(version, tag) { diff --git a/package.json b/package.json index afa6ca5d3..d58a6a5d0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.13.0", + "version": "3.12.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", @@ -61,4 +61,4 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } -} +} \ No newline at end of file From 672db9437f650c7882dca2fa3eecc2babbf5faa5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 01:55:56 +0000 Subject: [PATCH 55/83] Create ui project, add Lifecycle --- src/tsconfig.json | 3 ++- src/ui/Lifecycle.ts | 4 +--- src/ui/tsconfig.json | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 src/ui/tsconfig.json diff --git a/src/tsconfig.json b/src/tsconfig.json index f0b1c7490..1e8230a2d 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -24,6 +24,7 @@ ], "references": [ { "path": "./common" }, - { "path": "./core" } + { "path": "./core" }, + { "path": "./ui" } ] } diff --git a/src/ui/Lifecycle.ts b/src/ui/Lifecycle.ts index 9f058106a..4d36a1f12 100644 --- a/src/ui/Lifecycle.ts +++ b/src/ui/Lifecycle.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable } from 'xterm'; +import { IDisposable } from '../common/Types'; /** * Adds a disposable listener to a node in the DOM, returning the disposable. @@ -24,8 +24,6 @@ export function addDisposableDomListener( return; } node.removeEventListener(type, handler, useCapture); - node = null; - handler = null; } }; } diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json new file mode 100644 index 000000000..2763c6b15 --- /dev/null +++ b/src/ui/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig-library-base", + "compilerOptions": { + "lib": [ + "dom", + "es5", + ], + "outDir": "../../lib", + "types": [ + "../../node_modules/@types/mocha" + ] + }, + "include": [ + "./Lifecycle.ts" + ], + "references": [ + { "path": "../common" } + ] +} \ No newline at end of file From 4c3082c924ac2bd950edd2a16300d1d28469f5c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 01:59:40 +0000 Subject: [PATCH 56/83] Remove ITerminal dependency in RenderDebouncer --- src/AccessibilityManager.ts | 4 ++-- src/renderer/Renderer.ts | 4 ++-- src/renderer/dom/DomRenderer.ts | 4 ++-- src/ui/RenderDebouncer.ts | 15 ++++++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index e2115797f..1f44c1321 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -61,7 +61,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); - this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._refreshRows(); this._liveRegion = document.createElement('div'); @@ -239,7 +239,7 @@ export class AccessibilityManager extends Disposable { } private _refreshRows(start?: number, end?: number): void { - this._renderRowsDebouncer.refresh(start, end); + this._renderRowsDebouncer.refresh(start, end, this._terminal.rows); } private _renderRows(start: number, end: number): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2a205be36..14ce72b35 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -68,7 +68,7 @@ export class Renderer extends Disposable implements IRenderer { this._updateDimensions(); this.onOptionsChanged(); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); @@ -194,7 +194,7 @@ export class Renderer extends Disposable implements IRenderer { this._needsFullRefresh = true; return; } - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } /** diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 1d879fcd1..ea3ac5d98 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -80,7 +80,7 @@ export class DomRenderer extends Disposable implements IRenderer { }; this._updateDimensions(); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._rowFactory = new DomRendererRowFactory(_terminal.options, document); this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); @@ -340,7 +340,7 @@ export class DomRenderer extends Disposable implements IRenderer { } public refreshRows(start: number, end: number): void { - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } private _renderRows(start: number, end: number): void { diff --git a/src/ui/RenderDebouncer.ts b/src/ui/RenderDebouncer.ts index 775b7f748..260de12e9 100644 --- a/src/ui/RenderDebouncer.ts +++ b/src/ui/RenderDebouncer.ts @@ -1,4 +1,8 @@ -import { ITerminal } from '../Types'; +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IDisposable } from 'xterm'; /** @@ -7,10 +11,10 @@ import { IDisposable } from 'xterm'; export class RenderDebouncer implements IDisposable { private _rowStart: number; private _rowEnd: number; + private _rowCount: number; private _animationFrame: number = null; constructor( - private _terminal: ITerminal, private _callback: (start: number, end: number) => void ) { } @@ -22,10 +26,11 @@ export class RenderDebouncer implements IDisposable { } } - public refresh(rowStart: number, rowEnd: number): void { + public refresh(rowStart: number, rowEnd: number, rowCount: number): void { + this._rowCount = rowCount; // Get the min/max row start/end for the arg values rowStart = rowStart !== null && rowStart !== undefined ? rowStart : 0; - rowEnd = rowEnd !== null && rowEnd !== undefined ? rowEnd : this._terminal.rows - 1; + rowEnd = rowEnd !== null && rowEnd !== undefined ? rowEnd : this._rowCount - 1; // Check whether the row start/end values have already been set const isRowStartSet = this._rowStart !== undefined && this._rowStart !== null; const isRowEndSet = this._rowEnd !== undefined && this._rowEnd !== null; @@ -43,7 +48,7 @@ export class RenderDebouncer implements IDisposable { private _innerRefresh(): void { // Clamp values this._rowStart = Math.max(this._rowStart, 0); - this._rowEnd = Math.min(this._rowEnd, this._terminal.rows - 1); + this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1); // Run render callback this._callback(this._rowStart, this._rowEnd); From ebfa57b71cfc3f14f84144a5db6417043e655d3c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 02:03:41 +0000 Subject: [PATCH 57/83] Strict null check RenderDebouncer --- src/ui/RenderDebouncer.ts | 38 ++++++++++++++++++++------------------ src/ui/tsconfig.json | 3 ++- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ui/RenderDebouncer.ts b/src/ui/RenderDebouncer.ts index 260de12e9..ee2ab16e7 100644 --- a/src/ui/RenderDebouncer.ts +++ b/src/ui/RenderDebouncer.ts @@ -3,40 +3,37 @@ * @license MIT */ -import { IDisposable } from 'xterm'; +import { IDisposable } from '../common/Types'; /** * Debounces calls to render terminal rows using animation frames. */ export class RenderDebouncer implements IDisposable { - private _rowStart: number; - private _rowEnd: number; - private _rowCount: number; - private _animationFrame: number = null; + private _rowStart: number | undefined; + private _rowEnd: number | undefined; + private _rowCount: number | undefined; + private _animationFrame: number | undefined; constructor( - private _callback: (start: number, end: number) => void + private _renderCallback: (start: number, end: number) => void ) { } public dispose(): void { if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } public refresh(rowStart: number, rowEnd: number, rowCount: number): void { this._rowCount = rowCount; // Get the min/max row start/end for the arg values - rowStart = rowStart !== null && rowStart !== undefined ? rowStart : 0; - rowEnd = rowEnd !== null && rowEnd !== undefined ? rowEnd : this._rowCount - 1; - // Check whether the row start/end values have already been set - const isRowStartSet = this._rowStart !== undefined && this._rowStart !== null; - const isRowEndSet = this._rowEnd !== undefined && this._rowEnd !== null; + rowStart = rowStart !== undefined ? rowStart : 0; + rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1; // Set the properties to the updated values - this._rowStart = isRowStartSet ? Math.min(this._rowStart, rowStart) : rowStart; - this._rowEnd = isRowEndSet ? Math.max(this._rowEnd, rowEnd) : rowEnd; + this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; if (this._animationFrame) { return; @@ -46,16 +43,21 @@ export class RenderDebouncer implements IDisposable { } private _innerRefresh(): void { + // Make sure values are set + if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) { + return; + } + // Clamp values this._rowStart = Math.max(this._rowStart, 0); this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1); // Run render callback - this._callback(this._rowStart, this._rowEnd); + this._renderCallback(this._rowStart, this._rowEnd); // Reset debouncer - this._rowStart = null; - this._rowEnd = null; - this._animationFrame = null; + this._rowStart = undefined; + this._rowEnd = undefined; + this._animationFrame = undefined; } } diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json index 2763c6b15..672d464e9 100644 --- a/src/ui/tsconfig.json +++ b/src/ui/tsconfig.json @@ -11,7 +11,8 @@ ] }, "include": [ - "./Lifecycle.ts" + "./Lifecycle.ts", + "./RenderDebouncer.ts" ], "references": [ { "path": "../common" } From 88d5cf46f873deca4b3d47c10a538e01f5423089 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 02:16:28 +0000 Subject: [PATCH 58/83] Strict null check ScreenDprMonitor --- src/ui/ScreenDprMonitor.ts | 27 +++++++++++++++++---------- src/ui/tsconfig.json | 3 ++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ui/ScreenDprMonitor.ts b/src/ui/ScreenDprMonitor.ts index d66eeb64f..37c6a0dab 100644 --- a/src/ui/ScreenDprMonitor.ts +++ b/src/ui/ScreenDprMonitor.ts @@ -18,10 +18,10 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat * monitor with a different DPI. */ export class ScreenDprMonitor extends Disposable { - private _currentDevicePixelRatio: number; - private _outerListener: (this: MediaQueryList, ev: MediaQueryListEvent) => any; - private _listener: ScreenDprListener; - private _resolutionMediaMatchList: MediaQueryList; + private _currentDevicePixelRatio: number = window.devicePixelRatio; + private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; + private _listener: ScreenDprListener | undefined; + private _resolutionMediaMatchList: MediaQueryList | undefined; public setListener(listener: ScreenDprListener): void { if (this._listener) { @@ -29,6 +29,9 @@ export class ScreenDprMonitor extends Disposable { } this._listener = listener; this._outerListener = () => { + if (!this._listener) { + return; + } this._listener(window.devicePixelRatio, this._currentDevicePixelRatio); this._updateDpr(); }; @@ -41,10 +44,13 @@ export class ScreenDprMonitor extends Disposable { } private _updateDpr(): void { - // Clear listeners for old DPR - if (this._resolutionMediaMatchList) { - this._resolutionMediaMatchList.removeListener(this._outerListener); + if (!this._resolutionMediaMatchList || !this._outerListener) { + return; } + + // Clear listeners for old DPR + this._resolutionMediaMatchList.removeListener(this._outerListener); + // Add listeners for new DPR this._currentDevicePixelRatio = window.devicePixelRatio; this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`); @@ -52,11 +58,12 @@ export class ScreenDprMonitor extends Disposable { } public clearListener(): void { - if (!this._listener) { + if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) { return; } this._resolutionMediaMatchList.removeListener(this._outerListener); - this._listener = null; - this._outerListener = null; + this._resolutionMediaMatchList = undefined; + this._listener = undefined; + this._outerListener = undefined; } } diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json index 672d464e9..b3613eeff 100644 --- a/src/ui/tsconfig.json +++ b/src/ui/tsconfig.json @@ -12,7 +12,8 @@ }, "include": [ "./Lifecycle.ts", - "./RenderDebouncer.ts" + "./RenderDebouncer.ts", + "./ScreenDprMonitor.ts" ], "references": [ { "path": "../common" } From 362152004b39fc6572927b4eaed083a9d696e239 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 02:27:41 +0000 Subject: [PATCH 59/83] Move files not in ui project back to root --- src/Buffer.test.ts | 2 +- src/BufferSet.test.ts | 2 +- src/{ui => }/CharMeasure.test.ts | 2 +- src/{ui => }/CharMeasure.ts | 4 ++-- src/CharWidth.test.ts | 2 +- src/{ui => }/Clipboard.test.ts | 0 src/{ui => }/Clipboard.ts | 2 +- src/InputHandler.test.ts | 2 +- src/Linkifier.test.ts | 5 ++--- src/Linkifier.ts | 4 ++-- src/{ui => }/MouseHelper.test.ts | 0 src/{ui => }/MouseHelper.ts | 4 ++-- src/{ui => }/MouseZoneManager.ts | 7 +++--- src/SelectionManager.test.ts | 4 ++-- src/SelectionManager.ts | 4 ++-- src/SelectionModel.test.ts | 2 +- src/Terminal.test.ts | 2 +- src/Terminal.ts | 11 +++++----- src/{ui => }/TestUtils.test.ts | 14 ++++++------ src/Types.ts | 18 ++++++++++++++- src/Viewport.ts | 2 +- src/renderer/CharacterJoinerRegistry.test.ts | 2 +- src/ui/Types.ts | 23 -------------------- 23 files changed, 54 insertions(+), 64 deletions(-) rename src/{ui => }/CharMeasure.test.ts (97%) rename src/{ui => }/CharMeasure.ts (93%) rename src/{ui => }/Clipboard.test.ts (100%) rename src/{ui => }/Clipboard.ts (98%) rename src/{ui => }/MouseHelper.test.ts (100%) rename src/{ui => }/MouseHelper.ts (96%) rename src/{ui => }/MouseZoneManager.ts (97%) rename src/{ui => }/TestUtils.test.ts (97%) delete mode 100644 src/ui/Types.ts diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index d5a27bf1a..d866fd569 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; import { Buffer, DEFAULT_ATTR_DATA } from './Buffer'; import { CircularList } from './common/CircularList'; -import { MockTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockTerminal, TestTerminal } from './TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; const INIT_COLS = 80; diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 576a8ca47..cdc220b3f 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; describe('BufferSet', () => { let terminal: ITerminal; diff --git a/src/ui/CharMeasure.test.ts b/src/CharMeasure.test.ts similarity index 97% rename from src/ui/CharMeasure.test.ts rename to src/CharMeasure.test.ts index a3cb3b3b4..5fd17eb24 100644 --- a/src/ui/CharMeasure.test.ts +++ b/src/CharMeasure.test.ts @@ -4,7 +4,7 @@ */ import jsdom = require('jsdom'); -import { ICharMeasure } from '../Types'; +import { ICharMeasure } from './Types'; import { assert } from 'chai'; import { CharMeasure } from './CharMeasure'; diff --git a/src/ui/CharMeasure.ts b/src/CharMeasure.ts similarity index 93% rename from src/ui/CharMeasure.ts rename to src/CharMeasure.ts index c37df312a..efee89729 100644 --- a/src/ui/CharMeasure.ts +++ b/src/CharMeasure.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter2, IEvent } from '../common/EventEmitter2'; +import { ICharMeasure, ITerminalOptions } from './Types'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 8608c6fa3..ff6f17ede 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { TestTerminal } from './ui/TestUtils.test'; +import { TestTerminal } from './TestUtils.test'; import { assert } from 'chai'; import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; diff --git a/src/ui/Clipboard.test.ts b/src/Clipboard.test.ts similarity index 100% rename from src/ui/Clipboard.test.ts rename to src/Clipboard.test.ts diff --git a/src/ui/Clipboard.ts b/src/Clipboard.ts similarity index 98% rename from src/ui/Clipboard.ts rename to src/Clipboard.ts index 2570a8b17..7a32badcd 100644 --- a/src/ui/Clipboard.ts +++ b/src/Clipboard.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, ISelectionManager } from '../Types'; +import { ITerminal, ISelectionManager } from './Types'; interface IWindow extends Window { clipboardData?: { diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 01596f808..dcd4d1b46 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index c7bbbeb83..8f734bacb 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,10 +4,9 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone } from './ui/Types'; -import { ILinkMatcher, ITerminal, IBufferLine } from './Types'; +import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal, IBufferLine } from './Types'; import { Linkifier } from './Linkifier'; -import { MockBuffer, MockTerminal, TestTerminal } from './ui/TestUtils.test'; +import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; import { CircularList } from './common/CircularList'; import { BufferLine, CellData } from './BufferLine'; diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 8c57e1b22..7227013dc 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IMouseZoneManager } from './ui/Types'; +import { IMouseZoneManager } from './Types'; import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; -import { MouseZone } from './ui/MouseZoneManager'; +import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from './CharWidth'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; diff --git a/src/ui/MouseHelper.test.ts b/src/MouseHelper.test.ts similarity index 100% rename from src/ui/MouseHelper.test.ts rename to src/MouseHelper.test.ts diff --git a/src/ui/MouseHelper.ts b/src/MouseHelper.ts similarity index 96% rename from src/ui/MouseHelper.ts rename to src/MouseHelper.ts index e36e7f17c..fb91c9ef8 100644 --- a/src/ui/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { ICharMeasure, IMouseHelper } from '../Types'; -import { IRenderer } from '../renderer/Types'; +import { ICharMeasure, IMouseHelper } from './Types'; +import { IRenderer } from './renderer/Types'; export class MouseHelper implements IMouseHelper { constructor(private _renderer: IRenderer) {} diff --git a/src/ui/MouseZoneManager.ts b/src/MouseZoneManager.ts similarity index 97% rename from src/ui/MouseZoneManager.ts rename to src/MouseZoneManager.ts index 372dccc57..2f293df14 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { ITerminal } from '../Types'; -import { IMouseZoneManager, IMouseZone } from './Types'; -import { Disposable } from '../common/Lifecycle'; -import { addDisposableDomListener } from './Lifecycle'; +import { ITerminal, IMouseZoneManager, IMouseZone } from './Types'; +import { Disposable } from './common/Lifecycle'; +import { addDisposableDomListener } from './ui/Lifecycle'; const HOVER_DURATION = 500; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index d65f97160..9a2ac4051 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -4,12 +4,12 @@ */ import { assert } from 'chai'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer, IBufferLine } from './Types'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; class TestMockTerminal extends MockTerminal { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index eed206cc6..dcd600687 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -4,9 +4,9 @@ */ import { ITerminal, ISelectionManager, IBuffer, IBufferLine, ISelectionRedrawRequestEvent } from './Types'; -import { MouseHelper } from './ui/MouseHelper'; +import { MouseHelper } from './MouseHelper'; import * as Browser from './common/Platform'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index d49f41d0c..c2b261a98 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal } from './ui/TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index dd8e32e18..69fbb8e2e 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; -import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test'; +import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { CellData } from './BufferLine'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 16ac69b32..3fe5b5904 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,27 +21,26 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData } from './Types'; -import { IMouseZoneManager } from './ui/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData, IMouseZoneManager } from './Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR_DATA } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './ui/Clipboard'; +import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard'; import { C0 } from './common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import * as Browser from './common/Platform'; import { addDisposableDomListener } from './ui/Lifecycle'; import * as Strings from './Strings'; -import { MouseHelper } from './ui/MouseHelper'; +import { MouseHelper } from './MouseHelper'; import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; -import { MouseZoneManager } from './ui/MouseZoneManager'; +import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; import { ITheme, IMarker, IDisposable } from 'xterm'; diff --git a/src/ui/TestUtils.test.ts b/src/TestUtils.test.ts similarity index 97% rename from src/ui/TestUtils.test.ts rename to src/TestUtils.test.ts index 5ee0f9d5c..92018d1f1 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from '../Types'; -import { ICircularList, XtermListener } from '../common/Types'; -import { Buffer } from '../Buffer'; -import * as Browser from '../common/Platform'; +import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from './renderer/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from './Types'; +import { ICircularList, XtermListener } from './common/Types'; +import { Buffer } from './Buffer'; +import * as Browser from './common/Platform'; import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; -import { Terminal } from '../Terminal'; -import { AttributeData } from '../BufferLine'; +import { Terminal } from './Terminal'; +import { AttributeData } from './BufferLine'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/Types.ts b/src/Types.ts index a70dfdad2..e603e92b5 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -5,7 +5,6 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; -import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; import { IEvent } from './common/EventEmitter2'; @@ -604,3 +603,20 @@ export interface IBufferLine { isCombined(index: number): number; getString(index: number): string; } + +export interface IMouseZoneManager extends IDisposable { + add(zone: IMouseZone): void; + clearAll(start?: number, end?: number): void; +} + +export interface IMouseZone { + x1: number; + x2: number; + y1: number; + y2: number; + clickCallback: (e: MouseEvent) => any; + hoverCallback: (e: MouseEvent) => any | undefined; + tooltipCallback: (e: MouseEvent) => any | undefined; + leaveCallback: () => any | undefined; + willLinkActivate: (e: MouseEvent) => boolean; +} diff --git a/src/Viewport.ts b/src/Viewport.ts index 50d044571..6bf92531c 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -5,7 +5,7 @@ import { IColorSet } from './renderer/Types'; import { ITerminal, IViewport } from './Types'; -import { CharMeasure } from './ui/CharMeasure'; +import { CharMeasure } from './CharMeasure'; import { Disposable } from './common/Lifecycle'; import { addDisposableDomListener } from './ui/Lifecycle'; diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index effdbfaaf..2ccfd1978 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { MockTerminal, MockBuffer } from '../ui/TestUtils.test'; +import { MockTerminal, MockBuffer } from '../TestUtils.test'; import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; diff --git a/src/ui/Types.ts b/src/ui/Types.ts deleted file mode 100644 index 89dfa1a4f..000000000 --- a/src/ui/Types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'xterm'; - -export interface IMouseZoneManager extends IDisposable { - add(zone: IMouseZone): void; - clearAll(start?: number, end?: number): void; -} - -export interface IMouseZone { - x1: number; - x2: number; - y1: number; - y2: number; - clickCallback: (e: MouseEvent) => any; - hoverCallback: (e: MouseEvent) => any | undefined; - tooltipCallback: (e: MouseEvent) => any | undefined; - leaveCallback: () => any | undefined; - willLinkActivate: (e: MouseEvent) => boolean; -} From 4453a1cbf7b2f539956a60898876b341fd717788 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 03:52:08 +0000 Subject: [PATCH 60/83] Automatically release stable builds Part of #1921 --- bin/publish.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index ec7bf173d..d5e5047e9 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -11,9 +11,12 @@ const packageJson = require('../package.json'); // Setup auth fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`); -// Get the version -const tag = 'beta' -const nextVersion = getNextVersion(tag); +// Determine if this is a stable or beta release +const publishedVersions = getPublishedVersions(); +const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1; + +// Get the next version +let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(); console.log(`Publishing version: ${nextVersion}`); // Set the version in package.json @@ -22,16 +25,19 @@ packageJson.version = nextVersion; fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); // Publish -const result = cp.spawn('npm', ['publish', '--tag', tag], { - stdio: 'inherit' -}); +const args = ['publish']; +if (!isStableRelease) { + args.push('--tag', 'beta'); +} +const result = cp.spawn('npm', args, { stdio: 'inherit' }); result.on('exit', code => process.exit(code)); -function getNextVersion(tag) { +function getNextBetaVersion() { if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) { console.error('The package.json version must be of the form x.y.z'); process.exit(1); } + const tag = 'beta'; const stableVersion = packageJson.version.split('.'); const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; const publishedVersions = getPublishedVersions(nextStableVersion, tag); @@ -46,5 +52,8 @@ function getNextVersion(tag) { function getPublishedVersions(version, tag) { const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', 'versions', '--json']); const versionsJson = JSON.parse(versionsProcess.stdout); - return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + if (tag) { + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + } + return versionsJson; } From f0c3bab156ea5f32f0d824215ff86b14837ed3a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 29 Apr 2019 11:35:42 -0700 Subject: [PATCH 61/83] Add build and test task --- .vscode/tasks.json | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..2ff9f3cb7 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,48 @@ +{ + "version": "2.0.0", + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": true + }, + "tasks": [ + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "watch", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + }, + { + "type": "npm", + "script": "start", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + }, + { + "label": "Start demo", + "dependsOn": ["npm: watch", "npm: start"], + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + } + ] +} From 42b1dca7dbc3f9cb93b63f0cabdee4ecb06e0e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 29 Apr 2019 22:33:01 +0200 Subject: [PATCH 62/83] add handling for default fg color --- src/renderer/BaseRenderLayer.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 008b216a9..d851b2f26 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -322,14 +322,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else { this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else if (cell.isFgPalette()) { - let fg = cell.getFgColor(); - if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; + } else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else { + let fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; } - this._ctx.fillStyle = this._colors.ansi[fg].css; } this._clipRow(terminal, y); From 1f42e7c06aa617e8e7fc46e9fa0759e59779bcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 29 Apr 2019 23:19:16 +0200 Subject: [PATCH 63/83] fix fg color for underline --- src/renderer/TextRenderLayer.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 2547ecb24..c4bdf19fd 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -217,10 +217,18 @@ export class TextRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else if (cell.isFgPalette()) { - this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; + } else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else { + let fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; + } } this.fillBottomLineAtCells(x, y, cell.getWidth()); From 4fd9b3f249ae7788ff0664f76f875b3202fdced4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 30 Apr 2019 00:58:18 +0200 Subject: [PATCH 64/83] jix joined cell representation as overloaded CellData type --- src/renderer/BaseRenderLayer.ts | 5 +-- src/renderer/CharacterJoinerRegistry.ts | 47 +++++++++++++++++++++++-- src/renderer/TextRenderLayer.ts | 14 ++++---- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index d851b2f26..00b38dcd3 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -10,6 +10,7 @@ import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData } from '../BufferLine'; import { WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; +import { JoinedCellData } from './CharacterJoinerRegistry'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -260,8 +261,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - // skip cache right away if we draw in RGB - if (cell.isFgRGB() || cell.isBgRGB()) { + // skip cache right away if we draw in RGB or have joined cells + if (cell.isFgRGB() || cell.isBgRGB() || cell instanceof JoinedCellData) { this._drawUncachedChars(terminal, cell, x, y); return; } diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 8c521dd9d..6a47ee222 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,8 +1,51 @@ -import { ITerminal, IBufferLine } from '../Types'; +import { ITerminal, IBufferLine, ICellData, CharData } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData } from '../BufferLine'; +import { CellData, Content } from '../BufferLine'; import { WHITESPACE_CELL_CHAR } from '../Buffer'; +export class JoinedCellData extends CellData implements ICellData { + private _width: number = 0; + private _code: number = 0x1FFFFF; // highest allowed codepoint, meant as -1 + + public content: number = 0; + public fg: number = 0; + public bg: number = 0; + public combinedData: string = ''; + + constructor(firstCell: ICellData, chars: string, width: number) { + super(); + this.fg = firstCell.fg; + this.bg = firstCell.bg; + this.combinedData = chars; + this._width = width; + } + + public isCombined(): number { + // always mark joined cell data as combined + return Content.IS_COMBINED_MASK; + } + + public getWidth(): number { + return this._width; + } + + public getChars(): string { + return this.combinedData; + } + + public getCode(): number { + return this._code; + } + + public setFromCharData(value: CharData): void { + throw new Error('not implemented'); + } + + public getAsCharData(): CharData { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; + } +} + export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { private _characterJoiners: ICharacterJoiner[] = []; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index c4bdf19fd..d54008b8a 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -9,6 +9,7 @@ import { CharData, ITerminal, ICellData } from '../Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CellData, AttributeData, Content } from '../BufferLine'; +import { JoinedCellData } from './CharacterJoinerRegistry'; /** * This CharData looks like a null character, which will forc a clear and render @@ -89,15 +90,12 @@ export class TextRenderLayer extends BaseRenderLayer { // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly - cell = CellData.fromCharData([ - 0, + + cell = new JoinedCellData( + this._workCell, line.translateToString(true, range[0], range[1]), - range[1] - range[0], - 0xFFFFFF - ]); - // hacky: patch attrs - cell.fg = this._workCell.fg; - cell.bg = this._workCell.bg; + range[1] - range[0] + ); // Skip over the cells occupied by this range in the loop lastCharX = range[1] - 1; From 63833b59ed78c1bb5eb40f92dbcda6d845e5d97f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 29 Apr 2019 22:24:44 -0700 Subject: [PATCH 65/83] Fix lint --- src/Linkifier.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 7227013dc..9744929e1 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { IMouseZoneManager } from './Types'; -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult, IMouseZoneManager } from './Types'; import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from './CharWidth'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; From 469d5158a01d2d8bb5b18bc4066ba77c3cda581b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 30 Apr 2019 19:19:41 +0000 Subject: [PATCH 66/83] Make Terminal.markers readonly --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 24a30b256..b6d1e3207 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -359,7 +359,7 @@ declare module 'xterm' { * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. */ - readonly markers: IMarker[]; + readonly markers: ReadonlyArray; /** * Natural language strings that can be localized. From f416bd155a7e40f555c403431e828b37f7b9e1d6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 2 May 2019 07:53:28 -0700 Subject: [PATCH 67/83] Make compound task the default --- .vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2ff9f3cb7..2f752faf0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -37,7 +37,10 @@ { "label": "Start demo", "dependsOn": ["npm: watch", "npm: start"], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "isBackground": true, "problemMatcher": [], "presentation": { From ea09c6af09f89c3a2a1a6886a7daf9d3e4a79ea0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 2 May 2019 09:23:13 -0700 Subject: [PATCH 68/83] Fix build --- src/public/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index d05a4f100..70ce7db4d 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -30,7 +30,7 @@ export class Terminal implements ITerminalApi { public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } - public get markers(): IMarker[] { return this._core.markers; } + public get markers(): ReadonlyArray { return this._core.markers; } public blur(): void { this._core.blur(); } From 23eed1b3ea169fee0e1739fdd712bb256ff4892e Mon Sep 17 00:00:00 2001 From: roottool Date: Sat, 4 May 2019 02:02:26 +0900 Subject: [PATCH 69/83] Fix: Changed pathClause --- src/addons/webLinks/webLinks.test.ts | 166 +++++++++++++++++++++------ src/addons/webLinks/webLinks.ts | 3 +- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index 1e8a4ae7e..1423e0103 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -28,63 +28,159 @@ describe('webLinks addon', () => { }); }); - it('should allow ~ character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow simple URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/a~b#c~d?e~f '; + const row = ' http://foo.com '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + assert.equal(uri, 'http://foo.com'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io'); + }); }); - it('should allow : character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow ~ character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/colon:test '; + const row = ' http://foo.com/a~b#c~d?e~f '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/colon:test'); + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/a~b#c~d?e~f '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/a~b#c~d?e~f'); + }); }); - it('should not allow : character at the end of a URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow : character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/colon:test: '; + const row = ' http://foo.com/colon:test '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/colon:test'); + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); }); - it('should not allow " character at the end of a URI enclosed with ""', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should not allow : character at the end of a URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = '"http://foo.com/"'; + const row = ' http://foo.com/colon:test: '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/'); + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test: '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); }); - it('should not allow \' character at the end of a URI enclosed with \'\'', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should not allow " character at the end of a URI enclosed with ""', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = '\'http://foo.com/\''; + const row = '"http://foo.com/"'; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/'); + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '"http://bar.io/"'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); + }); + + describe('should not allow \' character at the end of a URI enclosed with \'\'', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://foo.com/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://bar.io/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index f0d69cc58..9ebc86929 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,8 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathClause = '(' + pathCharacterSet + ')?'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; From 4269777f1c17a48a570f57c7242c702e20222ab0 Mon Sep 17 00:00:00 2001 From: roottool Date: Sat, 4 May 2019 02:54:38 +0900 Subject: [PATCH 70/83] Fix: Refactored the css code of keyframes blink --- src/renderer/dom/DomRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index ea3ac5d98..01d5c7dce 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -174,9 +174,9 @@ export class DomRenderer extends Disposable implements IRenderer { // Blink animation styles += `@keyframes blink {` + - ` 0 % { opacity: 1.0; }` + + ` 0% { opacity: 1.0; }` + ` 50% { opacity: 0.0; }` + - ` 100 % { opacity: 1.0; }` + + ` 100% { opacity: 1.0; }` + `}`; // Cursor styles += From 613f8779546ceeb1405dcd07e7a8125d16ab26ab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 3 May 2019 21:07:21 -0700 Subject: [PATCH 71/83] Add a dev container This lets users using VS Code/Docker avoid installing node and the C++ dependencies necessary for running xterm.js. --- .devcontainer/Dockerfile | 22 ++++++++++++++++++++++ .devcontainer/devcontainer.json | 9 +++++++++ 2 files changed, 31 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..4d4e605b9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +FROM node:10 + +# Configure apt +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils 2>&1 + +# Verify git and process tools are installed +RUN apt-get install -y git procps + +# Install yarn +RUN apt-get install -y curl apt-transport-https lsb-release \ + && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ + && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get -y install --no-install-recommends yarn + +# Clean up +RUN apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* +ENV DEBIAN_FRONTEND=dialog diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..b00a26936 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "xterm.js", + "dockerFile": "Dockerfile", + "appPort": 3000, + "extensions": [ + "editorconfig.editorconfig", + "ms-vscode.vscode-typescript-tslint-plugin" + ] +} From ffcdba8d5b86231e5847fa913a0722da0b8f303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 5 May 2019 21:48:35 +0200 Subject: [PATCH 72/83] simplify JoinedCellData --- src/renderer/BaseRenderLayer.ts | 5 ++++- src/renderer/CharacterJoinerRegistry.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 00b38dcd3..ace1f6781 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -261,7 +261,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - // skip cache right away if we draw in RGB or have joined cells + // skip cache right away if we draw in RGB + // Note: to avoid bad runtime JoinedCellData will be skipped + // in the cache handler (atlasDidDraw == false) itself and + // fall through to uncached later down below if (cell.isFgRGB() || cell.isBgRGB() || cell instanceof JoinedCellData) { this._drawUncachedChars(terminal, cell, x, y); return; diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 6a47ee222..7a3bb8e05 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,15 +1,15 @@ import { ITerminal, IBufferLine, ICellData, CharData } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData, Content } from '../BufferLine'; +import { CellData, Content, AttributeData } from '../BufferLine'; import { WHITESPACE_CELL_CHAR } from '../Buffer'; -export class JoinedCellData extends CellData implements ICellData { - private _width: number = 0; - private _code: number = 0x1FFFFF; // highest allowed codepoint, meant as -1 - +export class JoinedCellData extends AttributeData implements ICellData { + private _width: number; + // .content carries no meaning for joined CellData, simply nullify it + // thus we have to overload all other .content accessors public content: number = 0; - public fg: number = 0; - public bg: number = 0; + public fg: number; + public bg: number; public combinedData: string = ''; constructor(firstCell: ICellData, chars: string, width: number) { @@ -34,7 +34,9 @@ export class JoinedCellData extends CellData implements ICellData { } public getCode(): number { - return this._code; + // code always gets the highest possible fake codepoint (read as -1) + // this is needed as code is used by caches as identifier + return 0x1FFFFF; } public setFromCharData(value: CharData): void { From a88469aac6edd162f6461112d19cecb6c8095c47 Mon Sep 17 00:00:00 2001 From: cubiclesoft Date: Tue, 7 May 2019 12:07:00 -0700 Subject: [PATCH 73/83] Added PHP App Server to Real-world uses section. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2e9f02f2..cbb2d6970 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,8 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation. - [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD - [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access. -- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems. +- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems. +- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From e831bdf1ef4636f5362fe8f3d941f905c46f84b6 Mon Sep 17 00:00:00 2001 From: Khaja Nizamuddin Date: Wed, 8 May 2019 21:06:22 +0530 Subject: [PATCH 74/83] Allow + char in URI path --- src/addons/webLinks/webLinks.test.ts | 26 ++++++++++++++++++++++++++ src/addons/webLinks/webLinks.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index 1423e0103..da5569ab8 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -183,4 +183,30 @@ describe('webLinks addon', () => { assert.equal(uri, 'http://bar.io/'); }); }); + + describe('should allow + character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://foo.com/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/subpath/+/id'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://bar.io/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/subpath/+/id'); + }); + }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index 9ebc86929..8a0fec097 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])'; const pathClause = '(' + pathCharacterSet + ')?'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; From 0d5cd9582fb5dd62d2ee6dbac714725410226c13 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 18:03:06 -0700 Subject: [PATCH 75/83] Base the next beta version on the correct current beta Fixes #2044 --- bin/publish.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index d5e5047e9..350cecebd 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -44,7 +44,14 @@ function getNextBetaVersion() { if (publishedVersions.length === 0) { return `${packageJson.version}-${tag}1`; } - const latestPublishedVersion = publishedVersions.sort((a, b) => b.localeCompare(a))[0]; + const latestPublishedVersion = publishedVersions.sort((a, b) => { + if (b.length > a.length) { + return true; + } else if (b.length < a.length) { + return false; + } + return b.localeCompare(a) + })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; } From 4b1c54d634bd2b6afc63a2626106ab9b58dd67aa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 20:53:24 -0700 Subject: [PATCH 76/83] Update terminal dimensions after line height changes Fixes #2048 --- demo/client.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index c73d81ddc..90a913d7d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -259,8 +259,11 @@ function initOptions(term: TerminalType): void { console.log('change', o, input.value); if (o === 'cols' || o === 'rows') { updateTerminalSize(); + } else if (o === 'lineHeight') { + term.setOption(o, parseFloat(input.value)); + updateTerminalSize(); } else { - term.setOption(o, o === 'lineHeight' ? parseFloat(input.value) : parseInt(input.value, 10)); + term.setOption(o, parseInt(input.value)); } }); }); From 71886167be39c4e43e4a0584eab3989196a5c271 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 23:08:39 -0700 Subject: [PATCH 77/83] Replace prepublish On prepublishOnly we want to do a full build, on prepare we want to only build the ts projects so the .d.ts files are avilable. Fixes #2019 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 539b96daf..41d3e52a8 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "mocha": "gulp test", "prebuild": "tsc -b ./src/tsconfig.all.json", "build": "gulp build", - "prepublish": "npm run build", + "prepare": "npm run prebuild", + "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } From dc236aeb204d303c91b69b50bf49a61a82e1b6fb Mon Sep 17 00:00:00 2001 From: Jared Flatow Date: Thu, 9 May 2019 00:06:32 -0700 Subject: [PATCH 78/83] Send the unit separator for C-_, like some other keyboards --- src/core/input/Keyboard.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index 5e4c4e617..36097f912 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -352,6 +352,10 @@ export function evaluateKeyboardEvent( } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) { // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc. result.key = ev.key; + } else if (ev.key && ev.ctrlKey) { + if (ev.key === '_') { // ^_ + result.key = C0.US; + } } break; } From b959fc8d6a1faa2674607e25af455c17bc9df6af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 07:49:50 -0700 Subject: [PATCH 79/83] Update year in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbb2d6970..83170eff5 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,6 @@ You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Co If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. -Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
+Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) From 79e182d63f8dcbe5f98462f5811771ddce294029 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 08:19:33 -0700 Subject: [PATCH 80/83] Actually fix beta CD Part of #2044 --- bin/publish.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 350cecebd..d55c9b97c 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -45,12 +45,9 @@ function getNextBetaVersion() { return `${packageJson.version}-${tag}1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { - if (b.length > a.length) { - return true; - } else if (b.length < a.length) { - return false; - } - return b.localeCompare(a) + const aVersion = parseInt(a.substr(a.search(/[0-9]+$/))); + const bVersion = parseInt(b.substr(b.search(/[0-9]+$/))); + return aVersion > bVersion ? -1 : 1; })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; From e40f9249134ca3f5038fb95fd4eb5f0d036b1228 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 09:37:07 -0700 Subject: [PATCH 81/83] v3.13.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d58a6a5d0..afa6ca5d3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.12.0", + "version": "3.13.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", @@ -61,4 +61,4 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } -} \ No newline at end of file +} From 3abc090412bf2f2d6728d4606fdfff39c1977d78 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 10:02:42 -0700 Subject: [PATCH 82/83] Add missing space to typings --- typings/xterm.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b6d1e3207..9f1fbe62d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -594,6 +594,7 @@ declare module 'xterm' { * @return An IDisposable you can call to remove this handler. */ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + /** * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * be matched and handled. From 6d55ab23fd38482008e873930b7a4ff32a8d7b70 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 10:11:25 -0700 Subject: [PATCH 83/83] Fix beta CD for new major/minor beta versions --- bin/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index d55c9b97c..3b13d6a5a 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -42,7 +42,7 @@ function getNextBetaVersion() { const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; const publishedVersions = getPublishedVersions(nextStableVersion, tag); if (publishedVersions.length === 0) { - return `${packageJson.version}-${tag}1`; + return `${nextStableVersion}-${tag}1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));