diff --git a/src/Buffer.ts b/src/Buffer.ts index 2d23980d..585855db 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -208,9 +208,19 @@ export class Buffer implements IBuffer { public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string { // Get full line let lineString = ''; - let widthAdjustedStartCol = startCol; - let widthAdjustedEndCol = endCol; const line = this.lines.get(lineIndex); + if (!line) { + return ''; + } + + // Initialize column and index values. Column values represent the actual + // cell column, indexes represent the index in the string. Indexes are + // needed here because some chars are 0 characters long (eg. after wide + // chars) and some chars are longer than 1 characters long (eg. emojis). + let startIndex = startCol; + endCol = endCol || line.length; + let endIndex = endCol; + for (let i = 0; i < line.length; i++) { const char = line[i]; lineString += char[CHAR_DATA_CHAR_INDEX]; @@ -218,29 +228,41 @@ export class Buffer implements IBuffer { // column indexes if (char[CHAR_DATA_WIDTH_INDEX] === 0) { if (startCol >= i) { - widthAdjustedStartCol--; + startIndex -= char[CHAR_DATA_CHAR_INDEX].length; } if (endCol >= i) { - widthAdjustedEndCol--; + endIndex -= char[CHAR_DATA_CHAR_INDEX].length; + } + } else { + // Adjust the columns to take glyphs that are represented by multiple + // code points into account. + if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + if (startCol >= i) { + startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; + } + if (endCol >= i) { + endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; + } } } + // TODO: startCol needs to be emoji-aware, currently each emoji code point is + // consuming addition space in the selection text } // Calculate the final end col by trimming whitespace on the right of the // line if needed. - let finalEndCol = widthAdjustedEndCol || line.length; if (trimRight) { const rightWhitespaceIndex = lineString.search(/\s+$/); if (rightWhitespaceIndex !== -1) { - finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); + endIndex = Math.min(endIndex, rightWhitespaceIndex); } // Return the empty string if only trimmed whitespace is selected - if (finalEndCol <= widthAdjustedStartCol) { + if (endIndex <= startIndex) { return ''; } } - return lineString.substring(widthAdjustedStartCol, finalEndCol); + return lineString.substring(startIndex, endIndex); } /** diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 28ed1522..65f6a57e 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -11,7 +11,7 @@ import { EventEmitter } from './EventEmitter'; import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces'; import { SelectionModel } from './SelectionModel'; import { LineData } from './Types'; -import { CHAR_DATA_WIDTH_INDEX } from './Buffer'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -281,6 +281,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Convert to 0-based coords[0]--; coords[1]--; + + console.log('coords', coords); + // Convert viewport coords to buffer coords coords[1] += this._terminal.buffer.ydisp; return coords; @@ -545,9 +548,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager for (let i = 0; coords[0] >= i; i++) { const char = bufferLine[i]; if (char[CHAR_DATA_WIDTH_INDEX] === 0) { + // Wide characters aren't included in the line string so decrement the index charIndex--; + // } + } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + console.log('char length > 1', char[CHAR_DATA_CHAR_INDEX], char[CHAR_DATA_CHAR_INDEX].length); + // Emojis take up multiple characters, so adjust accordingly + charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; } } + console.log('character index: ', charIndex); return charIndex; } @@ -606,20 +616,47 @@ export class SelectionManager extends EventEmitter implements ISelectionManager endCol++; } // Expand the string in both directions until a space is hit - while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) { - if (bufferLine[startCol - 1][CHAR_DATA_WIDTH_INDEX] === 0) { + let nextCharData = startIndex > 0 ? bufferLine[startCol - 1] : null; + + + +// TODO: Need to make sure that characters whose strings are longer than 1 get compensated for +// Double click words should expand to the spaces. + + + // while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) { + console.log('start char: ' + bufferLine[startCol]); + console.log('scan backwards'); + console.log(' startIndex:',startIndex); + while (startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1][CHAR_DATA_CHAR_INDEX])) { + const char = bufferLine[startCol - 1]; + console.log(' char: ' + char); + if (char[CHAR_DATA_WIDTH_INDEX] === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; startCol--; + } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1; +console.log('x', char[CHAR_DATA_CHAR_INDEX], char[CHAR_DATA_CHAR_INDEX].length); } startIndex--; startCol--; } - while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) { - if (bufferLine[endCol + 1][CHAR_DATA_WIDTH_INDEX] === 2) { + console.log('scan forwards'); + // while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) { + console.log(' first checking: ',bufferLine[endCol + 1]); + console.log(' endIndex:',endIndex); + console.log(' line:',line); + console.log(' line.length:',line.length); + while (endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1][CHAR_DATA_CHAR_INDEX])) { + const char = bufferLine[endCol + 1]; + console.log(' char: ' + char); + if (char[CHAR_DATA_WIDTH_INDEX] === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; endCol++; + } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; } endIndex++; endCol++;