diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index f154d2bc0f8e2..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,30 +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 - 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) @@ -2706,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 c097197c92749..b42c6dde00a97 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,9 +611,9 @@ 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, 4, 2, ' A'], + [2, 4, 4, 3, ' A'], [3, 4, 4, 3, ' A'], [0, 5, 5, 0, 'A'], ], 2); @@ -622,64 +622,64 @@ suite('TextModel.getLineIndentGuide', () => { 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, 2, 7, 1, 'class A {'], [1, 2, 7, 1, ''], - [1, 2, 7, 1, ' void foo() {'], - [2, 4, 5, 2, ' '], - [3, 5, 5, 3, ' return 1;'], - [1, 2, 7, 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 {'], + [0, 2, 7, 1, 'class A {'], [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, '}'] + [0, 2, 7, 1, '}'] ], 4); }); @@ -688,38 +688,38 @@ 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, 5, 2, ' emailAddress:'], [2, 3, 5, 2, ' - bla'], - [2, 3, 5, 2, ' - length:'], + [2, 5, 5, 3, ' - length:'], [3, 5, 5, 3, ' max: 255'], [0, 6, 6, 0, 'getters:'] ], 4); @@ -727,20 +727,20 @@ suite('TextModel.getLineIndentGuide', () => { 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, 6, 3, '\t\tcase 2:'], [3, 6, 6, 3, '\t\t\treturn 2;'], [1, 2, 7, 1, '\t}'], - [0, 8, 8, 0, '}'] + [0, 2, 7, 1, '}'] ], 4); }); test('issue #12398 - Problem in indent guidelines', () => { assertIndentGuides([ - [2, 1, 2, 2, '\t\t.bla'], + [2, 2, 2, 3, '\t\t.bla'], [3, 2, 2, 3, '\t\t\tlabel(for)'], [0, 3, 3, 0, 'include script'] ], 4); @@ -766,4 +766,36 @@ suite('TextModel.getLineIndentGuide', () => { assert.deepEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 }); model.dispose(); }); + + test('tweaks - no active', () => { + 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', () => { + assertIndentGuides([ + [0, 2, 2, 1, 'A'], + [1, 2, 2, 1, ' A'], + [0, 2, 2, 1, 'A'] + ], 2); + }); + + 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'] + ], 2); + }); });