mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Revert "Support wide characters and combined characters"
This reverts commit f961f90bc8.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user