From cfe6cd90cd3e3cbfc5d468a4e35a7b8e5bc65e11 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 3 Mar 2016 11:22:36 +0100 Subject: [PATCH] Fixes #3662: validate positions when converting them --- .../common/viewModel/splitLinesCollection.ts | 33 +++++-- .../viewModel/splitLinesCollection.test.ts | 94 ++++++++++++++++++- 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 1b2d9a44b94..785588fc3a7 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -622,8 +622,20 @@ export class SplitLinesCollection implements ILinesCollection { return this.prefixSumComputer.getTotalValue(); } + private _toValidOutputLineNumber(outputLineNumber: number): number { + if (outputLineNumber < 1) { + return 1; + } + let outputLineCount = this.getOutputLineCount(); + if (outputLineNumber > outputLineCount) { + return outputLineCount; + } + return outputLineNumber; + } + public getOutputLineContent(outputLineNumber: number): string { this._ensureValidState(); + outputLineNumber = this._toValidOutputLineNumber(outputLineNumber); this.prefixSumComputer.getIndexOf(outputLineNumber - 1, this.tmpIndexOfResult); var lineIndex = this.tmpIndexOfResult.index; var remainder = this.tmpIndexOfResult.remainder; @@ -633,6 +645,7 @@ export class SplitLinesCollection implements ILinesCollection { public getOutputLineMinColumn(outputLineNumber:number): number { this._ensureValidState(); + outputLineNumber = this._toValidOutputLineNumber(outputLineNumber); this.prefixSumComputer.getIndexOf(outputLineNumber - 1, this.tmpIndexOfResult); var lineIndex = this.tmpIndexOfResult.index; var remainder = this.tmpIndexOfResult.remainder; @@ -642,6 +655,7 @@ export class SplitLinesCollection implements ILinesCollection { public getOutputLineMaxColumn(outputLineNumber: number): number { this._ensureValidState(); + outputLineNumber = this._toValidOutputLineNumber(outputLineNumber); this.prefixSumComputer.getIndexOf(outputLineNumber - 1, this.tmpIndexOfResult); var lineIndex = this.tmpIndexOfResult.index; var remainder = this.tmpIndexOfResult.remainder; @@ -651,6 +665,7 @@ export class SplitLinesCollection implements ILinesCollection { public getOutputLineTokens(outputLineNumber: number, inaccurateTokensAcceptable: boolean): editorCommon.IViewLineTokens { this._ensureValidState(); + outputLineNumber = this._toValidOutputLineNumber(outputLineNumber); this.prefixSumComputer.getIndexOf(outputLineNumber - 1, this.tmpIndexOfResult); var lineIndex = this.tmpIndexOfResult.index; var remainder = this.tmpIndexOfResult.remainder; @@ -660,20 +675,23 @@ export class SplitLinesCollection implements ILinesCollection { public convertOutputPositionToInputPosition(viewLineNumber: number, viewColumn: number): editorCommon.IEditorPosition { this._ensureValidState(); + viewLineNumber = this._toValidOutputLineNumber(viewLineNumber); + this.prefixSumComputer.getIndexOf(viewLineNumber - 1, this.tmpIndexOfResult); var lineIndex = this.tmpIndexOfResult.index; var remainder = this.tmpIndexOfResult.remainder; var inputColumn = this.lines[lineIndex].getInputColumnOfOutputPosition(remainder, viewColumn); -// console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn); - return new Position(lineIndex+1, inputColumn); + // console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn); + return this.model.validatePosition(new Position(lineIndex+1, inputColumn)); } - public convertInputPositionToOutputPosition(inputLineNumber: number, inputColumn: number): editorCommon.IEditorPosition { + public convertInputPositionToOutputPosition(_inputLineNumber: number, _inputColumn: number): editorCommon.IEditorPosition { this._ensureValidState(); - if (inputLineNumber > this.lines.length) { - inputLineNumber = this.lines.length; - } + + let validPosition = this.model.validatePosition(new Position(_inputLineNumber, _inputColumn)); + let inputLineNumber = validPosition.lineNumber; + let inputColumn = validPosition.column; let lineIndex = inputLineNumber - 1, lineIndexChanged = false; while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { @@ -682,6 +700,7 @@ export class SplitLinesCollection implements ILinesCollection { } if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { // Could not reach a real line + // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } var deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); @@ -693,7 +712,7 @@ export class SplitLinesCollection implements ILinesCollection { r = this.lines[inputLineNumber - 1].getOutputPositionOfInputPosition(deltaLineNumber, inputColumn); } -// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r.column); + // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r); return r; } } \ No newline at end of file diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 1a5e4a72af6..36fdb2a70c0 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import {Position} from 'vs/editor/common/core/position'; -import {CharacterHardWrappingLineMapping} from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import {CharacterHardWrappingLineMapping, CharacterHardWrappingLineMapperFactory} from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; import {PrefixSumComputer} from 'vs/editor/common/viewModel/prefixSumComputer'; -import {ILineMapping, IModel, SplitLine} from 'vs/editor/common/viewModel/splitLinesCollection'; +import {ILineMapping, IModel, SplitLine, SplitLinesCollection} from 'vs/editor/common/viewModel/splitLinesCollection'; +import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; +import {Model} from 'vs/editor/common/model/model'; +import * as editorCommon from 'vs/editor/common/editorCommon'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { @@ -76,6 +79,93 @@ suite('Editor ViewModel - SplitLinesCollection', () => { assert.deepEqual(line1.getOutputPositionOfInputPosition(0, col), pos(2, 1 + col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')'); } }); + + test('issue #3662', () => { + let config = new MockConfiguration({}); + + let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( + config.editor.wordWrapBreakBeforeCharacters, + config.editor.wordWrapBreakAfterCharacters, + config.editor.wordWrapBreakObtrusiveCharacters + ); + + let model = new Model([ + 'int main() {', + '\tprintf("Hello world!");', + '}', + 'int main() {', + '\tprintf("Hello world!");', + '}', + ].join('\n'), editorCommon.DefaultEndOfLine.LF, null); + + let linesCollection = new SplitLinesCollection( + model, + hardWrappingLineMapperFactory, + config.getIndentationOptions().tabSize, + config.editor.wrappingInfo.wrappingColumn, + config.editor.typicalFullwidthCharacterWidth / config.editor.typicalHalfwidthCharacterWidth, + editorCommon.wrappingIndentFromString(config.editor.wrappingIndent) + ); + + linesCollection.setHiddenAreas([{ + startLineNumber: 1, + startColumn: 1, + endLineNumber: 3, + endColumn: 1 + }, { + startLineNumber: 5, + startColumn: 1, + endLineNumber: 6, + endColumn: 1 + }], (eventType, payload) => {/*no-op*/}); + + let viewLineCount = linesCollection.getOutputLineCount(); + assert.equal(viewLineCount, 1, 'getOutputLineCount()'); + + let modelLineCount = model.getLineCount(); + for (let lineNumber = 0; lineNumber <= modelLineCount + 1; lineNumber++) { + let lineMinColumn = (lineNumber >= 1 && lineNumber <= modelLineCount) ? model.getLineMinColumn(lineNumber) : 1; + let lineMaxColumn = (lineNumber >= 1 && lineNumber <= modelLineCount) ? model.getLineMaxColumn(lineNumber) : 1; + for (let column = lineMinColumn - 1; column <= lineMaxColumn + 1; column++) { + let viewPosition = linesCollection.convertInputPositionToOutputPosition(lineNumber, column); + + // validate view position + let viewLineNumber = viewPosition.lineNumber; + let viewColumn = viewPosition.column; + if (viewLineNumber < 1) { + viewLineNumber = 1; + } + var lineCount = linesCollection.getOutputLineCount(); + if (viewLineNumber > lineCount) { + viewLineNumber = lineCount; + } + var viewMinColumn = linesCollection.getOutputLineMinColumn(viewLineNumber); + var viewMaxColumn = linesCollection.getOutputLineMaxColumn(viewLineNumber); + if (viewColumn < viewMinColumn) { + viewColumn = viewMinColumn; + } + if (viewColumn > viewMaxColumn) { + viewColumn = viewMaxColumn; + } + let validViewPosition = new Position(viewLineNumber, viewColumn); + assert.equal(viewPosition.toString(), validViewPosition.toString(), 'model->view for ' + lineNumber + ', ' + column); + } + } + + for (let lineNumber = 0; lineNumber <= viewLineCount + 1; lineNumber++) { + let lineMinColumn = linesCollection.getOutputLineMinColumn(lineNumber); + let lineMaxColumn = linesCollection.getOutputLineMaxColumn(lineNumber); + for (let column = lineMinColumn - 1; column <= lineMaxColumn + 1; column++) { + let modelPosition = linesCollection.convertOutputPositionToInputPosition(lineNumber, column); + let validModelPosition = model.validatePosition(modelPosition); + assert.equal(modelPosition.toString(), validModelPosition.toString(), 'view->model for ' + lineNumber + ', ' + column); + } + } + + linesCollection.dispose(); + model.dispose(); + config.dispose(); + }); });