diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 360a051d067..1e13c300c87 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -148,7 +148,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe } } - result[i] = new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength); + result[i] = new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength, null, null, null); } document.body.removeChild(containerDomNode); diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index 66d9e367817..14badf0e688 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -22,12 +22,14 @@ export class LineTokens implements IViewLineTokens { private readonly _tokensCount: number; private readonly _text: string; + public static defaultTokenMetadata = ( + (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET) + | (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET) + | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) + ) >>> 0; + public static createEmpty(lineContent: string): LineTokens { - const defaultMetadata = ( - (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET) - | (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET) - | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) - ) >>> 0; + const defaultMetadata = LineTokens.defaultTokenMetadata; const tokens = new Uint32Array(2); tokens[0] = lineContent.length; @@ -165,6 +167,57 @@ export class LineTokens implements IViewLineTokens { return low; } + + /** + * @pure + */ + public withInserted(insertTokens: { offset: number, text: string, tokenMetadata: number }[]): LineTokens { + if (insertTokens.length === 0) { + return this; + } + + insertTokens.sort((t1, t2) => t1.offset - t2.offset); + + let nextOriginalTokenIdx = 0; + let nextInsertTokenIdx = 0; + + let text = ''; + const newTokens = new Array(); + let delta = 0; + + let originalEndOffset = 0; + while (true) { + let nextOriginalTokenEndOffset = nextOriginalTokenIdx < this._tokensCount ? this._tokens[nextOriginalTokenIdx << 1] : null; + let nextInsertToken = nextInsertTokenIdx < insertTokens.length ? insertTokens[nextInsertTokenIdx] : null; + + if (nextOriginalTokenEndOffset !== null && (nextInsertToken === null || nextOriginalTokenEndOffset <= nextInsertToken.offset)) { + // original token ends before next insert token + text += this._text.substring(originalEndOffset, nextOriginalTokenEndOffset); + const metadata = this._tokens[(nextOriginalTokenIdx << 1) + 1]; + newTokens.push(nextOriginalTokenEndOffset + delta, metadata); + nextOriginalTokenIdx++; + originalEndOffset = nextOriginalTokenEndOffset; + + } else if (nextInsertToken) { + if (nextInsertToken.offset > originalEndOffset) { + // insert token is in the middle of the next token. + text += this._text.substring(originalEndOffset, nextInsertToken.offset); + const metadata = this._tokens[(nextOriginalTokenIdx << 1) + 1]; + newTokens.push(nextInsertToken.offset + delta, metadata); + originalEndOffset = nextInsertToken.offset; + } + + text += nextInsertToken.text; + newTokens.push(nextInsertToken.offset + delta + nextInsertToken.text.length, nextInsertToken.tokenMetadata); + delta += nextInsertToken.text.length; + nextInsertTokenIdx++; + } else { + break; + } + } + + return new LineTokens(new Uint32Array(newTokens), text); + } } export class SlicedLineTokens implements IViewLineTokens { diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index a0bea771332..29f2b021bac 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -738,6 +738,8 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len if (tokenIndex < tokensLength) { tokenType = tokens[tokenIndex].type; tokenEndIndex = tokens[tokenIndex].endIndex; + } else { + break; } } } diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 043b8d5643f..7772b4b118a 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -357,9 +357,6 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla } function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: string, injectedText: LineInjectedText[] | null, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null { - if (injectedText) { - console.log(`TODO: `, injectedText); - } if (firstLineBreakColumn === -1) { return null; } @@ -444,7 +441,20 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: str breakingOffsets[breakingOffsetsCount] = len; breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + let injectionTexts: string[] | null; + let injectionOffsets: number[] | null; + let injectionWidths: number[] | null; + if (injectedText) { + injectionTexts = injectedText.map(t => t.text); + injectionOffsets = injectedText.map(text => text.column - 1); + injectionWidths = injectedText.map(text => text.text.length); + } else { + injectionTexts = null; + injectionOffsets = null; + injectionWidths = null; + } + + return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength, injectionTexts, injectionOffsets, injectionWidths); } function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 808fac953e1..02dc0f48c22 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -1219,14 +1219,14 @@ export class SplitLine implements ISplitLine { } private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { - return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, 0); + return this._lineBreakData.getInputOffsetOfOutputPosition(outputLineIndex, 0); } private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) { return model.getLineMaxColumn(modelLineNumber) - 1; } - return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex + 1, 0); + return this._lineBreakData.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { @@ -1293,29 +1293,51 @@ export class SplitLine implements ISplitLine { let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex); let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex); - let lineContent = model.getValueInRange({ - startLineNumber: modelLineNumber, - startColumn: startOffset + 1, - endLineNumber: modelLineNumber, - endColumn: endOffset + 1 - }); + const lineBreakData = this._lineBreakData; + console.log(lineBreakData.injectionTexts, startOffset, endOffset); - if (outputLineIndex > 0) { - lineContent = spaces(this._lineBreakData.wrappedTextIndentLength) + lineContent; + let lineTokens: LineTokens; + //lineTokens. + //console.log(lineTokens); + + const offsets = lineBreakData.injectionOffsets; + //const widths = lineBreakData.injectionWidth; + const texts = lineBreakData.injectionTexts; + if (offsets) { + lineTokens = model.getLineTokens(modelLineNumber).withInserted(offsets.map((offset, idx) => ({ + offset, + text: texts![idx], + tokenMetadata: LineTokens.defaultTokenMetadata + }))); + + } else { + lineTokens = model.getLineTokens(modelLineNumber); } - let minColumn = (outputLineIndex > 0 ? this._lineBreakData.wrappedTextIndentLength + 1 : 1); + let lineContent = lineBreakData.injectionTexts !== null + ? lineTokens.getLineContent().substring(startOffset, endOffset) + : model.getValueInRange({ + startLineNumber: modelLineNumber, + startColumn: startOffset + 1, + endLineNumber: modelLineNumber, + endColumn: endOffset + 1 + }); + + if (outputLineIndex > 0) { + lineContent = spaces(lineBreakData.wrappedTextIndentLength) + lineContent; + } + + let minColumn = (outputLineIndex > 0 ? lineBreakData.wrappedTextIndentLength + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { - deltaStartIndex = this._lineBreakData.wrappedTextIndentLength; + deltaStartIndex = lineBreakData.wrappedTextIndentLength; } - let lineTokens = model.getLineTokens(modelLineNumber); - const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]); + const startVisibleColumn = (outputLineIndex === 0 ? 0 : lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]); return new ViewLineData( lineContent, @@ -1354,14 +1376,14 @@ export class SplitLine implements ISplitLine { adjustedColumn -= this._lineBreakData.wrappedTextIndentLength; } } - return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, adjustedColumn) + 1; + return this._lineBreakData.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { if (!this._isVisible) { throw new Error('Not supported'); } - let r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1); + let r = this._lineBreakData.getOutputPositionOfInputOffset(inputColumn - 1); let outputLineIndex = r.outputLineIndex; let outputColumn = r.outputOffset + 1; @@ -1377,7 +1399,7 @@ export class SplitLine implements ISplitLine { if (!this._isVisible) { throw new Error('Not supported'); } - const r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1); + const r = this._lineBreakData.getOutputPositionOfInputOffset(inputColumn - 1); return (deltaLineNumber + r.outputLineIndex); } diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 0e3330611c4..7256ca17743 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -113,28 +113,64 @@ export class LineBreakData { constructor( public breakOffsets: number[], public breakOffsetsVisibleColumn: number[], - public wrappedTextIndentLength: number + public wrappedTextIndentLength: number, + /** + * Is null if there are no injections. + */ + //public textWithInjections: string | null, + public injectionTexts: string[] | null, + public injectionOffsets: number[] | null, + public injectionWidth: number[] | null, ) { } - public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { + public getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number { + let inputOffset = 0; if (outputLineIndex === 0) { - return outputOffset; + inputOffset = outputOffset; } else { - return breakOffsets[outputLineIndex - 1] + outputOffset; + inputOffset = this.breakOffsets[outputLineIndex - 1] + outputOffset; } + + if (this.injectionOffsets !== null) { + for (let i = 0; i < this.injectionOffsets.length; i++) { + if (inputOffset > this.injectionOffsets[i]) { + inputOffset -= Math.min(inputOffset - this.injectionOffsets[i], this.injectionTexts![i].length); + } else { + break; + } + } + } + + // 0 1 2 3 4 5 6 7 8 9 + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // x x x x x x x x x x + // 4 2 + + return inputOffset; } - public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition { + public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition { + let delta = 0; + if (this.injectionOffsets !== null) { + for (let i = 0; i < this.injectionOffsets.length; i++) { + if (inputOffset < this.injectionOffsets[i]) { + break; + } + delta += this.injectionTexts![i].length; + } + } + inputOffset += delta; + let low = 0; - let high = breakOffsets.length - 1; + let high = this.breakOffsets.length - 1; let mid = 0; let midStart = 0; while (low <= high) { mid = low + ((high - low) / 2) | 0; - const midStop = breakOffsets[mid]; - midStart = mid > 0 ? breakOffsets[mid - 1] : 0; + const midStop = this.breakOffsets[mid]; + midStart = mid > 0 ? this.breakOffsets[mid - 1] : 0; if (inputOffset < midStart) { high = mid - 1; @@ -145,6 +181,7 @@ export class LineBreakData { } } + return new OutputPosition(mid, inputOffset - midStart); } } diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index b98a4a1f7c8..1ad3606387b 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -30,7 +30,7 @@ function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): str if (lineBreakData) { let previousLineIndex = 0; for (let i = 0, len = text.length; i < len; i++) { - let r = LineBreakData.getOutputPositionOfInputOffset(lineBreakData.breakOffsets, i); + let r = lineBreakData.getOutputPositionOfInputOffset(i); if (previousLineIndex !== r.outputLineIndex) { previousLineIndex = r.outputLineIndex; actualAnnotatedText += '|'; @@ -64,7 +64,7 @@ function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, maxDigitWidth: 7 }, false); const lineBreaksComputer = factory.createLineBreaksComputer(fontInfo, tabSize, breakAfter, wrappingIndent); - const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength) : null; + const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength, null, null, null) : null; lineBreaksComputer.addRequest(text, null, previousLineBreakDataClone); return lineBreaksComputer.finalize()[0]; } diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 3f797895235..e6d1d48334f 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -778,7 +778,7 @@ function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleCo for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); + return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth, null, null, null); } function createModel(text: string): ISimpleModel {