From 78236850f3fd6370758df24a1075ba1cbd6ed8c6 Mon Sep 17 00:00:00 2001 From: Cailin Smith Date: Mon, 22 Jul 2019 17:16:56 +0200 Subject: [PATCH 1/5] Move indent right one when the highlighted line is a scope start/end Previously, the indent was always to the left of the current line. While this logic was easier to implement, it is inconsistent with the behavior expected. This commit changes it to select the scope, rather than the line's indent. This is not a complete fix for #49342, as this does not add support to semantic detection, however, this will work in probably 90% of the cases, and is a relatively straightforward fix. --- src/vs/editor/common/model/textModel.ts | 25 +++++ .../common/model/textModelWithTokens.test.ts | 98 +++++++++++++------ 2 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3b7bb2d136536..aa7b8da805ba8 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2408,6 +2408,31 @@ export class TextModel extends Disposable implements model.ITextModel { if (distance === 0) { // This is the initial line number + if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber) > currentIndent) { + // This is a new scope, we have special handling here, since we want the + // child scope's indent to be active, not the parent scope's + startLineNumber = upLineNumber + 1; + endLineNumber = maxLineNumber; + for (let offsetDown = startLineNumber; offsetDown < maxLineNumber; offsetDown++) { + if (this._computeIndentLevel(offsetDown) === currentIndent) { + endLineNumber = offsetDown; + break; + } + } + return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; + } + if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber - 2) > currentIndent) { + // This is the end of a scope. Like above, but we walk backwards to find the parent scope start + endLineNumber = upLineNumber - 1; + startLineNumber = minLineNumber; + for (let offsetUp = endLineNumber; offsetUp >= minLineNumber; offsetUp--) { + if (this._computeIndentLevel(offsetUp - 1) === this._computeIndentLevel(upLineNumber - 1)) { + startLineNumber = offsetUp + 1; + break; + } + } + return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; + } startLineNumber = upLineNumber; endLineNumber = downLineNumber; indent = upLineIndentLevel; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 74d9319043949..fe47c6f9f57c9 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -433,37 +433,6 @@ suite('TextModel.getLineIndentGuide', () => { assert.deepEqual(actual, lines); - // Also test getActiveIndentGuide - for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) { - let startLineNumber = lineNumber; - let endLineNumber = lineNumber; - let indent = actualIndents[lineNumber - 1]; - - if (indent !== 0) { - for (let i = lineNumber - 1; i >= 1; i--) { - const currIndent = actualIndents[i - 1]; - if (currIndent >= indent) { - startLineNumber = i; - } else { - break; - } - } - for (let i = lineNumber + 1; i <= model.getLineCount(); i++) { - const currIndent = actualIndents[i - 1]; - if (currIndent >= indent) { - endLineNumber = i; - } else { - break; - } - } - } - - const expected = { startLineNumber, endLineNumber, indent }; - const actual = model.getActiveIndentGuide(lineNumber, 1, model.getLineCount()); - - assert.deepEqual(actual, expected, `line number ${lineNumber}`); - } - model.dispose(); } @@ -644,3 +613,70 @@ suite('TextModel.getLineIndentGuide', () => { model.dispose(); }); }); + +suite('TextModel.getActiveIndentGuide', () => { + function assertActiveIndent(lines: [Boolean, string][], selectedLine: number) { + let text = lines.map(l => l[1]).join('\n'); + let model = TextModel.createFromString(text); + + let actualActive = model.getActiveIndentGuide(selectedLine, 1, model.getLineCount()); + + let actual: [Boolean, string][] = []; + for (let line = 1; line <= model.getLineCount(); line++) { + actual[line - 1] = [actualActive.indent > 0 && line >= actualActive.startLineNumber && line <= actualActive.endLineNumber, model.getLineContent(line)]; + } + + assert.deepEqual(actual, lines); + + model.dispose(); + + } + + test('no active', () => { + assertActiveIndent([ + [false, 'A'], + [false, 'A'] + ], 1); + }); + + test('inside scope', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'] + ], 2); + }); + + test('scope start', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 1); + }); + + test('inside scope start and end', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 2); + }); + + test('scope end', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 3); + }); + + test('empty line', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [true, ''], + [true, ' A'], + [false, 'A'] + ], 3); + }); +}); \ No newline at end of file From 82838194fdd8723944be195350863ae77be79f43 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Feb 2020 15:00:00 +0100 Subject: [PATCH 2/5] Use assertIndentGuides for new tests --- .../common/model/textModelWithTokens.test.ts | 85 ++++++------------- 1 file changed, 25 insertions(+), 60 deletions(-) diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 0528def36db28..99dc7b08963b5 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -765,71 +765,36 @@ suite('TextModel.getLineIndentGuide', () => { assert.deepEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 }); model.dispose(); }); -}); - -suite('TextModel.getActiveIndentGuide', () => { - function assertActiveIndent(lines: [Boolean, string][], selectedLine: number) { - let text = lines.map(l => l[1]).join('\n'); - let model = TextModel.createFromString(text); - - let actualActive = model.getActiveIndentGuide(selectedLine, 1, model.getLineCount()); - - let actual: [Boolean, string][] = []; - for (let line = 1; line <= model.getLineCount(); line++) { - actual[line - 1] = [actualActive.indent > 0 && line >= actualActive.startLineNumber && line <= actualActive.endLineNumber, model.getLineContent(line)]; - } - - assert.deepEqual(actual, lines); - - model.dispose(); - - } - - test('no active', () => { - assertActiveIndent([ - [false, 'A'], - [false, 'A'] - ], 1); - }); - test('inside scope', () => { - assertActiveIndent([ - [false, 'A'], - [true, ' A'] - ], 2); - }); - - test('scope start', () => { - assertActiveIndent([ - [false, 'A'], - [true, ' A'], - [false, 'A'] - ], 1); + test('tweaks - no active', () => { + assertIndentGuides([ + [0, 1, 1, 0, 'A'], + [0, 2, 2, 0, 'A'] + ]); }); - test('inside scope start and end', () => { - assertActiveIndent([ - [false, 'A'], - [true, ' A'], - [false, 'A'] - ], 2); + test('tweaks - inside scope', () => { + assertIndentGuides([ + [0, 2, 2, 1, 'A'], + [1, 2, 2, 1, ' A'] + ]); }); - test('scope end', () => { - assertActiveIndent([ - [false, 'A'], - [true, ' A'], - [false, 'A'] - ], 3); + test('tweaks - scope start', () => { + assertIndentGuides([ + [0, 2, 2, 1, 'A'], + [1, 2, 2, 1, ' A'], + [0, 2, 2, 1, 'A'] + ]); }); - test('empty line', () => { - assertActiveIndent([ - [false, 'A'], - [true, ' A'], - [true, ''], - [true, ' A'], - [false, 'A'] - ], 3); + test('tweaks - empty line', () => { + assertIndentGuides([ + [0, 2, 4, 1, 'A'], + [1, 2, 4, 1, ' A'], + [1, 2, 4, 1, ''], + [1, 2, 4, 1, ' A'], + [0, 2, 4, 1, 'A'] + ]); }); -}); \ No newline at end of file +}); From cef5c5a7285c3816019e2e7d9d2e0318e9b9d8f1 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Feb 2020 15:17:59 +0100 Subject: [PATCH 3/5] Have the tests green --- .../common/model/textModelWithTokens.test.ts | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 61d95192b3d81..d813d1b9a6db2 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -592,7 +592,7 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide one level 2', () => { assertIndentGuides([ - [0, 1, 1, 0, 'A'], + [0, 2, 4, 1, 'A'], [1, 2, 4, 1, ' A'], [1, 2, 4, 1, ' A'], [1, 2, 4, 1, ' A'], @@ -601,9 +601,9 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide two levels', () => { assertIndentGuides([ - [0, 1, 1, 0, 'A'], - [1, 2, 5, 1, ' A'], + [0, 2, 5, 1, 'A'], [1, 2, 5, 1, ' A'], + [1, 4, 5, 2, ' A'], [2, 4, 5, 2, ' A'], [2, 4, 5, 2, ' A'], ], 2); @@ -611,60 +611,60 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide three levels', () => { assertIndentGuides([ - [0, 1, 1, 0, 'A'], - [1, 2, 4, 1, ' A'], - [2, 3, 4, 2, ' A'], + [0, 2, 4, 1, 'A'], + [1, 3, 5, 2, ' A'], // TODO 5 => 4 + [2, 4, 5, 3, ' A'], // TODO 5 => 4 [3, 4, 4, 3, ' A'], - [0, 5, 5, 0, 'A'], + [0, 2, 4, 1, 'A'], // TODO => too big of a difference with the line above? ], 2); }); test('getLineIndentGuide decreasing indent', () => { assertIndentGuides([ [2, 1, 1, 2, ' A'], - [1, 1, 2, 1, ' A'], - [0, 3, 3, 0, 'A'], + [1, 1, 1, 2, ' A'], + [0, 1, 2, 1, 'A'], ], 2); }); test('getLineIndentGuide Java', () => { assertIndentGuides([ - /* 1*/[0, 1, 1, 0, 'class A {'], - /* 2*/[1, 2, 9, 1, ' void foo() {'], + /* 1*/[0, 2, 9, 1, 'class A {'], + /* 2*/[1, 3, 4, 2, ' void foo() {'], /* 3*/[2, 3, 4, 2, ' console.log(1);'], /* 4*/[2, 3, 4, 2, ' console.log(2);'], - /* 5*/[1, 2, 9, 1, ' }'], + /* 5*/[1, 3, 4, 2, ' }'], /* 6*/[1, 2, 9, 1, ''], - /* 7*/[1, 2, 9, 1, ' void bar() {'], + /* 7*/[1, 8, 8, 2, ' void bar() {'], /* 8*/[2, 8, 8, 2, ' console.log(3);'], - /* 9*/[1, 2, 9, 1, ' }'], - /*10*/[0, 10, 10, 0, '}'], - /*11*/[0, 11, 11, 0, 'interface B {'], + /* 9*/[1, 8, 8, 2, ' }'], + /*10*/[0, 2, 9, 1, '}'], + /*11*/[0, 12, 12, 1, 'interface B {'], /*12*/[1, 12, 12, 1, ' void bar();'], - /*13*/[0, 13, 13, 0, '}'], + /*13*/[0, 12, 12, 1, '}'], ], 2); }); test('getLineIndentGuide Javadoc', () => { assertIndentGuides([ - [0, 1, 1, 0, '/**'], + [0, 2, 3, 1, '/**'], [1, 2, 3, 1, ' * Comment'], [1, 2, 3, 1, ' */'], - [0, 4, 4, 0, 'class A {'], + [0, 5, 6, 1, 'class A {'], [1, 5, 6, 1, ' void foo() {'], [1, 5, 6, 1, ' }'], - [0, 7, 7, 0, '}'], + [0, 5, 6, 1, '}'], ], 2); }); test('getLineIndentGuide Whitespace', () => { assertIndentGuides([ - [0, 1, 1, 0, 'class A {'], + [0, 1, 1, 0, 'class A {'], // TODO => the next one is not working here [1, 2, 7, 1, ''], - [1, 2, 7, 1, ' void foo() {'], + [1, 2, 7, 1, ' void foo() {'], // TODO => also not working here [2, 4, 5, 2, ' '], [3, 5, 5, 3, ' return 1;'], - [1, 2, 7, 1, ' }'], + [1, 4, 5, 2, ' }'], [1, 2, 7, 1, ' '], [0, 8, 8, 0, '}'] ], 2); @@ -672,12 +672,12 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide Tabs', () => { assertIndentGuides([ - [0, 1, 1, 0, 'class A {'], + [0, 1, 1, 0, 'class A {'], // TODO => not working here [1, 2, 7, 1, '\t\t'], - [1, 2, 7, 1, '\tvoid foo() {'], + [1, 4, 5, 2, '\tvoid foo() {'], [2, 4, 5, 2, '\t \t//hello'], [2, 4, 5, 2, '\t return 2;'], - [1, 2, 7, 1, ' \t}'], + [1, 4, 5, 2, ' \t}'], [1, 2, 7, 1, ' '], [0, 8, 8, 0, '}'] ], 4); @@ -688,61 +688,61 @@ suite('TextModel.getLineIndentGuide', () => { /* 1*/[0, 1, 1, 0, '/// '], /* 2*/[0, 2, 2, 0, ''], /* 3*/[0, 3, 3, 0, '/* @internal */'], - /* 4*/[0, 4, 4, 0, 'namespace ts {'], + /* 4*/[0, 5, 16, 1, 'namespace ts {'], /* 5*/[1, 5, 16, 1, ' let nextSymbolId = 1;'], /* 6*/[1, 5, 16, 1, ' let nextNodeId = 1;'], /* 7*/[1, 5, 16, 1, ' let nextMergeId = 1;'], /* 8*/[1, 5, 16, 1, ' let nextFlowId = 1;'], /* 9*/[1, 5, 16, 1, ''], - /*10*/[1, 5, 16, 1, ' export function getNodeId(node: Node): number {'], - /*11*/[2, 11, 15, 2, ' if (!node.id) {'], + /*10*/[1, 11, 15, 2, ' export function getNodeId(node: Node): number {'], + /*11*/[2, 12, 13, 3, ' if (!node.id) {'], /*12*/[3, 12, 13, 3, ' node.id = nextNodeId;'], /*13*/[3, 12, 13, 3, ' nextNodeId++;'], - /*14*/[2, 11, 15, 2, ' }'], + /*14*/[2, 12, 13, 3, ' }'], /*15*/[2, 11, 15, 2, ' return node.id;'], - /*16*/[1, 5, 16, 1, ' }'], - /*17*/[0, 17, 17, 0, '}'] + /*16*/[1, 11, 15, 2, ' }'], + /*17*/[0, 5, 16, 1, '}'] ], 4); }); test('issue #8425 - Missing indentation lines for first level indentation', () => { assertIndentGuides([ - [1, 1, 4, 1, '\tindent1'], + [1, 2, 3, 2, '\tindent1'], [2, 2, 3, 2, '\t\tindent2'], [2, 2, 3, 2, '\t\tindent2'], - [1, 1, 4, 1, '\tindent1'] + [1, 2, 3, 2, '\tindent1'] ], 4); }); test('issue #8952 - Indentation guide lines going through text on .yml file', () => { assertIndentGuides([ - [0, 1, 1, 0, 'properties:'], - [1, 2, 5, 1, ' emailAddress:'], + [0, 2, 5, 1, 'properties:'], + [1, 3, 6, 2, ' emailAddress:'], // TODO 6 => 5 [2, 3, 5, 2, ' - bla'], - [2, 3, 5, 2, ' - length:'], + [2, 5, 6, 3, ' - length:'], // TODO 6 => 5 [3, 5, 5, 3, ' max: 255'], - [0, 6, 6, 0, 'getters:'] + [0, 2, 5, 1, 'getters:'] ], 4); }); test('issue #11892 - Indent guides look funny', () => { assertIndentGuides([ - [0, 1, 1, 0, 'function test(base) {'], - [1, 2, 7, 1, '\tswitch (base) {'], - [2, 3, 6, 2, '\t\tcase 1:'], + [0, 2, 7, 1, 'function test(base) {'], + [1, 3, 6, 2, '\tswitch (base) {'], + [2, 4, 4, 3, '\t\tcase 1:'], [3, 4, 4, 3, '\t\t\treturn 1;'], - [2, 3, 6, 2, '\t\tcase 2:'], + [2, 6, 8, 3, '\t\tcase 2:'], // TODO 8 => 6 or 7 [3, 6, 6, 3, '\t\t\treturn 2;'], - [1, 2, 7, 1, '\t}'], - [0, 8, 8, 0, '}'] + [1, 3, 6, 2, '\t}'], + [0, 2, 7, 1, '}'] ], 4); }); test('issue #12398 - Problem in indent guidelines', () => { assertIndentGuides([ - [2, 1, 2, 2, '\t\t.bla'], + [2, 2, 3, 3, '\t\t.bla'], [3, 2, 2, 3, '\t\t\tlabel(for)'], - [0, 3, 3, 0, 'include script'] + [0, 1, 2, 1, 'include script'] ], 4); }); @@ -771,14 +771,14 @@ suite('TextModel.getLineIndentGuide', () => { assertIndentGuides([ [0, 1, 1, 0, 'A'], [0, 2, 2, 0, 'A'] - ]); + ], 2); }); test('tweaks - inside scope', () => { assertIndentGuides([ [0, 2, 2, 1, 'A'], [1, 2, 2, 1, ' A'] - ]); + ], 2); }); test('tweaks - scope start', () => { @@ -786,7 +786,7 @@ suite('TextModel.getLineIndentGuide', () => { [0, 2, 2, 1, 'A'], [1, 2, 2, 1, ' A'], [0, 2, 2, 1, 'A'] - ]); + ], 2); }); test('tweaks - empty line', () => { @@ -796,6 +796,6 @@ suite('TextModel.getLineIndentGuide', () => { [1, 2, 4, 1, ''], [1, 2, 4, 1, ' A'], [0, 2, 4, 1, 'A'] - ]); + ], 2); }); }); From 2bee07a136380aa2884dc59caa7db60a8da425ff Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Feb 2020 15:29:29 +0100 Subject: [PATCH 4/5] Use outer loop to goDown in special case --- src/vs/editor/common/model/textModel.ts | 24 ++++++++----------- .../common/model/textModelWithTokens.test.ts | 12 +++++----- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3f00bc891f1f0..dd69add93d44e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2675,19 +2675,13 @@ export class TextModel extends Disposable implements model.ITextModel { if (distance === 0) { // This is the initial line number if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber) > currentIndent) { - // This is a new scope, we have special handling here, since we want the - // child scope's indent to be active, not the parent scope's + // This is the beginning of a scope, we have special handling here, since we want the + // child scope indent to be active, not the parent scope + goUp = false; startLineNumber = upLineNumber + 1; endLineNumber = maxLineNumber; - for (let offsetDown = startLineNumber; offsetDown < maxLineNumber; offsetDown++) { - if (this._computeIndentLevel(offsetDown) === currentIndent) { - endLineNumber = offsetDown; - break; - } - } - return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; - } - if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber - 2) > currentIndent) { + indent = upLineIndentLevel + 1; + } else if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber - 2) > currentIndent) { // This is the end of a scope. Like above, but we walk backwards to find the parent scope start endLineNumber = upLineNumber - 1; startLineNumber = minLineNumber; @@ -2698,10 +2692,12 @@ export class TextModel extends Disposable implements model.ITextModel { } } return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; + } else { + startLineNumber = upLineNumber; + endLineNumber = downLineNumber; + indent = upLineIndentLevel; } - startLineNumber = upLineNumber; - endLineNumber = downLineNumber; - indent = upLineIndentLevel; + if (indent === 0) { // No need to continue return { startLineNumber, endLineNumber, indent }; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index d813d1b9a6db2..cf8c345e620e6 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -612,8 +612,8 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide three levels', () => { assertIndentGuides([ [0, 2, 4, 1, 'A'], - [1, 3, 5, 2, ' A'], // TODO 5 => 4 - [2, 4, 5, 3, ' A'], // TODO 5 => 4 + [1, 3, 4, 2, ' A'], + [2, 4, 4, 3, ' A'], [3, 4, 4, 3, ' A'], [0, 2, 4, 1, 'A'], // TODO => too big of a difference with the line above? ], 2); @@ -717,9 +717,9 @@ suite('TextModel.getLineIndentGuide', () => { test('issue #8952 - Indentation guide lines going through text on .yml file', () => { assertIndentGuides([ [0, 2, 5, 1, 'properties:'], - [1, 3, 6, 2, ' emailAddress:'], // TODO 6 => 5 + [1, 3, 5, 2, ' emailAddress:'], [2, 3, 5, 2, ' - bla'], - [2, 5, 6, 3, ' - length:'], // TODO 6 => 5 + [2, 5, 5, 3, ' - length:'], [3, 5, 5, 3, ' max: 255'], [0, 2, 5, 1, 'getters:'] ], 4); @@ -731,7 +731,7 @@ suite('TextModel.getLineIndentGuide', () => { [1, 3, 6, 2, '\tswitch (base) {'], [2, 4, 4, 3, '\t\tcase 1:'], [3, 4, 4, 3, '\t\t\treturn 1;'], - [2, 6, 8, 3, '\t\tcase 2:'], // TODO 8 => 6 or 7 + [2, 6, 6, 3, '\t\tcase 2:'], [3, 6, 6, 3, '\t\t\treturn 2;'], [1, 3, 6, 2, '\t}'], [0, 2, 7, 1, '}'] @@ -740,7 +740,7 @@ suite('TextModel.getLineIndentGuide', () => { test('issue #12398 - Problem in indent guidelines', () => { assertIndentGuides([ - [2, 2, 3, 3, '\t\t.bla'], + [2, 2, 2, 3, '\t\t.bla'], [3, 2, 2, 3, '\t\t\tlabel(for)'], [0, 1, 2, 1, 'include script'] ], 4); From 06c034b92e88fe1f39613f61466e4b1128fd664d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 6 Feb 2020 16:14:28 +0100 Subject: [PATCH 5/5] Use first lines with content to do special cases --- src/vs/editor/common/model/textModel.ts | 94 ++++++++++--------- .../common/model/textModelWithTokens.test.ts | 22 ++--- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index dd69add93d44e..26293a466bb78 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2640,14 +2640,16 @@ export class TextModel extends Disposable implements model.ITextModel { let goDown = true; let indent = 0; + let initialIndent = 0; + for (let distance = 0; goUp || goDown; distance++) { const upLineNumber = lineNumber - distance; const downLineNumber = lineNumber + distance; - if (distance !== 0 && (upLineNumber < 1 || upLineNumber < minLineNumber)) { + if (distance > 1 && (upLineNumber < 1 || upLineNumber < minLineNumber)) { goUp = false; } - if (distance !== 0 && (downLineNumber > lineCount || downLineNumber > maxLineNumber)) { + if (distance > 1 && (downLineNumber > lineCount || downLineNumber > maxLineNumber)) { goDown = false; } if (distance > 50000) { @@ -2656,10 +2658,9 @@ export class TextModel extends Disposable implements model.ITextModel { goDown = false; } + let upLineIndentLevel: number = -1; if (goUp) { // compute indent level going up - let upLineIndentLevel: number; - const currentIndent = this._computeIndentLevel(upLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) @@ -2671,51 +2672,11 @@ export class TextModel extends Disposable implements model.ITextModel { up_resolveIndents(upLineNumber); upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent); } - - if (distance === 0) { - // This is the initial line number - if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber) > currentIndent) { - // This is the beginning of a scope, we have special handling here, since we want the - // child scope indent to be active, not the parent scope - goUp = false; - startLineNumber = upLineNumber + 1; - endLineNumber = maxLineNumber; - indent = upLineIndentLevel + 1; - } else if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber - 2) > currentIndent) { - // This is the end of a scope. Like above, but we walk backwards to find the parent scope start - endLineNumber = upLineNumber - 1; - startLineNumber = minLineNumber; - for (let offsetUp = endLineNumber; offsetUp >= minLineNumber; offsetUp--) { - if (this._computeIndentLevel(offsetUp - 1) === this._computeIndentLevel(upLineNumber - 1)) { - startLineNumber = offsetUp + 1; - break; - } - } - return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; - } else { - startLineNumber = upLineNumber; - endLineNumber = downLineNumber; - indent = upLineIndentLevel; - } - - if (indent === 0) { - // No need to continue - return { startLineNumber, endLineNumber, indent }; - } - continue; - } - - if (upLineIndentLevel >= indent) { - startLineNumber = upLineNumber; - } else { - goUp = false; - } } + let downLineIndentLevel = -1; if (goDown) { // compute indent level going down - let downLineIndentLevel: number; - const currentIndent = this._computeIndentLevel(downLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) @@ -2727,7 +2688,50 @@ export class TextModel extends Disposable implements model.ITextModel { down_resolveIndents(downLineNumber); downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent); } + } + + if (distance === 0) { + initialIndent = upLineIndentLevel; + continue; + } + + if (distance === 1) { + if (downLineNumber <= lineCount && downLineIndentLevel >= 0 && initialIndent + 1 === downLineIndentLevel) { + // This is the beginning of a scope, we have special handling here, since we want the + // child scope indent to be active, not the parent scope + goUp = false; + startLineNumber = downLineNumber; + endLineNumber = downLineNumber; + indent = downLineIndentLevel; + continue; + } + if (upLineNumber >= 1 && upLineIndentLevel >= 0 && upLineIndentLevel - 1 === initialIndent) { + // This is the end of a scope, just like above + goDown = false; + startLineNumber = upLineNumber; + endLineNumber = upLineNumber; + indent = upLineIndentLevel; + continue; + } + + startLineNumber = lineNumber; + endLineNumber = lineNumber; + indent = initialIndent; + if (indent === 0) { + // No need to continue + return { startLineNumber, endLineNumber, indent }; + } + } + + if (goUp) { + if (upLineIndentLevel >= indent) { + startLineNumber = upLineNumber; + } else { + goUp = false; + } + } + if (goDown) { if (downLineIndentLevel >= indent) { endLineNumber = downLineNumber; } else { diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index cf8c345e620e6..b42c6dde00a97 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -615,7 +615,7 @@ suite('TextModel.getLineIndentGuide', () => { [1, 3, 4, 2, ' A'], [2, 4, 4, 3, ' A'], [3, 4, 4, 3, ' A'], - [0, 2, 4, 1, 'A'], // TODO => too big of a difference with the line above? + [0, 5, 5, 0, 'A'], ], 2); }); @@ -659,27 +659,27 @@ suite('TextModel.getLineIndentGuide', () => { test('getLineIndentGuide Whitespace', () => { assertIndentGuides([ - [0, 1, 1, 0, 'class A {'], // TODO => the next one is not working here + [0, 2, 7, 1, 'class A {'], [1, 2, 7, 1, ''], - [1, 2, 7, 1, ' void foo() {'], // TODO => also not working here - [2, 4, 5, 2, ' '], - [3, 5, 5, 3, ' return 1;'], + [1, 4, 5, 2, ' void foo() {'], + [2, 4, 5, 2, ' '], + [2, 4, 5, 2, ' return 1;'], [1, 4, 5, 2, ' }'], [1, 2, 7, 1, ' '], - [0, 8, 8, 0, '}'] + [0, 2, 7, 1, '}'] ], 2); }); test('getLineIndentGuide Tabs', () => { assertIndentGuides([ - [0, 1, 1, 0, 'class A {'], // TODO => not working here + [0, 2, 7, 1, 'class A {'], [1, 2, 7, 1, '\t\t'], [1, 4, 5, 2, '\tvoid foo() {'], [2, 4, 5, 2, '\t \t//hello'], [2, 4, 5, 2, '\t return 2;'], [1, 4, 5, 2, ' \t}'], [1, 2, 7, 1, ' '], - [0, 8, 8, 0, '}'] + [0, 2, 7, 1, '}'] ], 4); }); @@ -721,7 +721,7 @@ suite('TextModel.getLineIndentGuide', () => { [2, 3, 5, 2, ' - bla'], [2, 5, 5, 3, ' - length:'], [3, 5, 5, 3, ' max: 255'], - [0, 2, 5, 1, 'getters:'] + [0, 6, 6, 0, 'getters:'] ], 4); }); @@ -733,7 +733,7 @@ suite('TextModel.getLineIndentGuide', () => { [3, 4, 4, 3, '\t\t\treturn 1;'], [2, 6, 6, 3, '\t\tcase 2:'], [3, 6, 6, 3, '\t\t\treturn 2;'], - [1, 3, 6, 2, '\t}'], + [1, 2, 7, 1, '\t}'], [0, 2, 7, 1, '}'] ], 4); }); @@ -742,7 +742,7 @@ suite('TextModel.getLineIndentGuide', () => { assertIndentGuides([ [2, 2, 2, 3, '\t\t.bla'], [3, 2, 2, 3, '\t\t\tlabel(for)'], - [0, 1, 2, 1, 'include script'] + [0, 3, 3, 0, 'include script'] ], 4); });