fix: translate string offset to buffer index in wrapped line

This commit is contained in:
Gerald
2021-03-02 11:31:26 +08:00
parent ae037bdac0
commit db42dcfae2
+56 -39
View File
@@ -35,7 +35,7 @@ export class SearchAddon implements ITerminalAddon {
* We memoize the calls into an array that has a time based ttl. * We memoize the calls into an array that has a time based ttl.
* _linesCache is also invalidated when the terminal cursor moves. * _linesCache is also invalidated when the terminal cursor moves.
*/ */
private _linesCache: string[] | undefined; private _linesCache: [string, number[]][] | undefined;
private _linesCacheTimeoutId = 0; private _linesCacheTimeoutId = 0;
private _cursorMoveListener: IDisposable | undefined; private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined; private _resizeListener: IDisposable | undefined;
@@ -258,7 +258,7 @@ export class SearchAddon implements ITerminalAddon {
*/ */
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
const terminal = this._terminal!; const terminal = this._terminal!;
let row = searchPosition.startRow; const row = searchPosition.startRow;
const col = searchPosition.startCol; const col = searchPosition.startCol;
// Ignore wrapped lines, only consider on unwrapped line (first row of command string). // Ignore wrapped lines, only consider on unwrapped line (first row of command string).
@@ -275,13 +275,14 @@ export class SearchAddon implements ITerminalAddon {
searchPosition.startCol += terminal.cols; searchPosition.startCol += terminal.cols;
return this._findInLine(term, searchPosition, searchOptions); return this._findInLine(term, searchPosition, searchOptions);
} }
let stringLine = this._linesCache ? this._linesCache[row] : void 0; let cache = this._linesCache?.[row];
if (stringLine === void 0) { if (!cache) {
stringLine = this._translateBufferLineToStringWithWrap(row, true); cache = this._translateBufferLineToStringWithWrap(row, true);
if (this._linesCache) { if (this._linesCache) {
this._linesCache[row] = stringLine; this._linesCache[row] = cache;
} }
} }
const [stringLine, offsets] = cache;
const offset = this._bufferColsToStringOffset(row, col); const offset = this._bufferColsToStringOffset(row, col);
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase(); const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
@@ -316,51 +317,57 @@ export class SearchAddon implements ITerminalAddon {
} }
if (resultIndex >= 0) { if (resultIndex >= 0) {
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
if (resultIndex >= terminal.cols) {
row += Math.floor(resultIndex / terminal.cols);
resultIndex = resultIndex % terminal.cols;
}
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) { if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
return; return;
} }
const line = terminal.buffer.active.getLine(row); // Adjust the row number and search index if needed since a "line" of text can span multiple rows
let size = term.length; let startRowOffset = 0;
while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) {
let col = 0; startRowOffset++;
if (line) {
col = this._stringLengthToBufferSize(line, resultIndex);
size = this._stringLengthToBufferSize(line, term.length, col);
} }
let endRowOffset = startRowOffset;
while (endRowOffset < offsets.length - 1 && resultIndex + term.length >= offsets[endRowOffset + 1]) {
endRowOffset++;
}
const startColOffset = resultIndex - offsets[startRowOffset];
const endColOffset = resultIndex + term.length - offsets[endRowOffset];
const startColIndex = this._stringLengthToBufferSize(row + startRowOffset, startColOffset);
const endColIndex = this._stringLengthToBufferSize(row + endRowOffset, endColOffset);
const size = endColIndex - startColIndex + terminal.cols * (endRowOffset - startRowOffset);
return { return {
term, term,
col, col: startColIndex,
row, row: row + startRowOffset,
size size
}; };
} }
} }
private _stringLengthToBufferSize(line: IBufferLine, length: number, start: number = 0): number { private _stringLengthToBufferSize(row: number, offset: number): number {
for (let i = 0; i < length; i++) { const line = this._terminal!.buffer.active.getLine(row);
const cell = line.getCell(start + i); if (!line) {
return 0;
}
for (let i = 0; i < offset; i++) {
const cell = line.getCell(i);
if (!cell) { if (!cell) {
break; break;
} }
// Adjust the searchIndex to normalize emoji into single chars // Adjust the searchIndex to normalize emoji into single chars
const char = cell.getChars(); const char = cell.getChars();
if (char.length > 1) { if (char.length > 1) {
length -= char.length - 1; offset -= char.length - 1;
} }
// Adjust the searchIndex for empty characters following wide unicode // Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK) // chars (eg. CJK)
const nextCell = line.getCell(start + i + 1); const nextCell = line.getCell(i + 1);
if (nextCell && nextCell.getWidth() === 0) { if (nextCell && nextCell.getWidth() === 0) {
length++; offset++;
} }
} }
return length; return offset;
} }
private _bufferColsToStringOffset(startRow: number, cols: number): number { private _bufferColsToStringOffset(startRow: number, cols: number): number {
@@ -396,23 +403,33 @@ export class SearchAddon implements ITerminalAddon {
* @param line The line being translated. * @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right. * @param trimRight Whether to trim whitespace to the right.
*/ */
private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string { private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): [string, number[]] {
const terminal = this._terminal!; const terminal = this._terminal!;
let lineString = ''; const strings = [];
let lineWrapsToNext: boolean; const offsets = [0];
let line = terminal.buffer.active.getLine(lineIndex);
do { while (line) {
const nextLine = terminal.buffer.active.getLine(lineIndex + 1); const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false; const lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.active.getLine(lineIndex); let string = line.translateToString(!lineWrapsToNext && trimRight);
if (!line) { if (lineWrapsToNext && nextLine) {
const lastCell = line.getCell(line.length - 1);
const lastCellIsNull = lastCell && lastCell.getCode() === 0 && lastCell.getWidth() === 1;
// a wide character wrapped to the next line
if (lastCellIsNull && nextLine.getCell(0)?.getWidth() === 2) {
string = string.slice(0, -1);
}
}
strings.push(string);
if (lineWrapsToNext) {
offsets.push(offsets[offsets.length - 1] + string.length);
} else {
break; break;
} }
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
lineIndex++; lineIndex++;
} while (lineWrapsToNext); line = nextLine;
}
return lineString; return [strings.join(''), offsets];
} }
/** /**