Repository: microsoft/vscode (TypeScript)
Date: 2021-08-07 00:53:03+00:00
Task type: Bug Fix

Issue:
Folding Navigation Commands Sometimes Don't Work
Testing #129353

![recording](https://user-images.githubusercontent.com/2931520/127130120-2db11bf2-d7e3-4fce-9e9a-e66ea9d1a1df.gif)

index.js
```js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = void 0;
const vscode = require("vscode");
function activate(context) {
    vscode.window.showInformationMessage('hello world');
}
exports.activate = activate;
//# sourceMappingURL=index.js.map
```

There is a foldable element, but "Go to next fold" does nothing. I'm hitting F1 -> Enter.
Folding Navigation Commands Sometimes Don't Work
Testing #129353

![recording](https://user-images.githubusercontent.com/2931520/127130120-2db11bf2-d7e3-4fce-9e9a-e66ea9d1a1df.gif)

index.js
```js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = void 0;
const vscode = require("vscode");
function activate(context) {
    vscode.window.showInformationMessage('hello world');
}
exports.activate = activate;
//# sourceMappingURL=index.js.map
```

There is a foldable element, but "Go to next fold" does nothing. I'm hitting F1 -> Enter.


Issue discussion:
Same here with python code (`Go to next fold` doesn't work regardless of where the cursor is):
```python
if True:
    print('Two')

if True:
    print('Two')
```
@aeschli and @DonJayamanne, I can take a look.  I think it's because those folds don't have a parent fold.
Same here with python code (`Go to next fold` doesn't work regardless of where the cursor is):
```python
if True:
    print('Two')

if True:
    print('Two')
```
@aeschli and @DonJayamanne, I can take a look.  I think it's because those folds don't have a parent fold.

Solution patch:
diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts
--- a/src/vs/editor/contrib/folding/foldingModel.ts
+++ b/src/vs/editor/contrib/folding/foldingModel.ts
@@ -461,10 +461,10 @@ export function getPreviousFoldLine(lineNumber: number, foldingModel: FoldingMod
 		} else {
 			// Find min line number to stay within parent.
 			let expectedParentIndex = foldingRegion.parentIndex;
-			if (expectedParentIndex === -1) {
-				return null;
+			let minLineNumber = 0;
+			if (expectedParentIndex !== -1) {
+				minLineNumber = foldingModel.regions.getStartLineNumber(foldingRegion.parentIndex);
 			}
-			let minLineNumber = foldingModel.regions.getStartLineNumber(foldingRegion.parentIndex);
 
 			// Find fold at same level.
 			while (foldingRegion !== null) {
@@ -482,6 +482,22 @@ export function getPreviousFoldLine(lineNumber: number, foldingModel: FoldingMod
 				}
 			}
 		}
+	} else {
+		// Go to last fold that's before the current line.
+		if (foldingModel.regions.length > 0) {
+			foldingRegion = foldingModel.regions.toRegion(foldingModel.regions.length - 1);
+			while (foldingRegion !== null) {
+				// Found non-parent fold before current line.
+				if (foldingRegion.parentIndex === -1 && foldingRegion.startLineNumber < lineNumber) {
+					return foldingRegion.startLineNumber;
+				}
+				if (foldingRegion.regionIndex > 0) {
+					foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex - 1);
+				} else {
+					foldingRegion = null;
+				}
+			}
+		}
 	}
 	return null;
 }
@@ -498,10 +514,14 @@ export function getNextFoldLine(lineNumber: number, foldingModel: FoldingModel):
 	if (foldingRegion !== null) {
 		// Find max line number to stay within parent.
 		let expectedParentIndex = foldingRegion.parentIndex;
-		if (expectedParentIndex === -1) {
+		let maxLineNumber = 0;
+		if (expectedParentIndex !== -1) {
+			maxLineNumber = foldingModel.regions.getEndLineNumber(foldingRegion.parentIndex);
+		} else if (foldingModel.regions.length === 0) {
 			return null;
+		} else {
+			maxLineNumber = foldingModel.regions.getEndLineNumber(foldingModel.regions.length - 1);
 		}
-		let maxLineNumber = foldingModel.regions.getEndLineNumber(foldingRegion.parentIndex);
 
 		// Find fold at same level.
 		while (foldingRegion !== null) {
@@ -518,6 +538,22 @@ export function getNextFoldLine(lineNumber: number, foldingModel: FoldingModel):
 				return null;
 			}
 		}
+	} else {
+		// Go to first fold that's after the current line.
+		if (foldingModel.regions.length > 0) {
+			foldingRegion = foldingModel.regions.toRegion(0);
+			while (foldingRegion !== null) {
+				// Found non-parent fold after current line.
+				if (foldingRegion.parentIndex === -1 && foldingRegion.startLineNumber > lineNumber) {
+					return foldingRegion.startLineNumber;
+				}
+				if (foldingRegion.regionIndex < foldingModel.regions.length) {
+					foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex + 1);
+				} else {
+					foldingRegion = null;
+				}
+			}
+		}
 	}
 	return null;
 }


Test patch:
diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts
--- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts
+++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts
@@ -887,4 +887,45 @@ suite('Folding Model', () => {
 		}
 
 	});
+
+	test('fold jumping issue #129503', () => {
+		let lines = [
+			/* 1*/	'',
+			/* 2*/	'if True:',
+			/* 3*/	'  print(1)',
+			/* 4*/	'if True:',
+			/* 5*/	'  print(1)',
+			/* 6*/	''
+		];
+
+		let textModel = createTextModel(lines.join('\n'));
+		try {
+			let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
+
+			let ranges = computeRanges(textModel, false, undefined);
+			foldingModel.update(ranges);
+
+			let r1 = r(2, 3, false);
+			let r2 = r(4, 6, false);
+			assertRanges(foldingModel, [r1, r2]);
+
+			// Test jump to next.
+			assert.strictEqual(getNextFoldLine(1, foldingModel), 2);
+			assert.strictEqual(getNextFoldLine(2, foldingModel), 4);
+			assert.strictEqual(getNextFoldLine(3, foldingModel), 4);
+			assert.strictEqual(getNextFoldLine(4, foldingModel), null);
+			assert.strictEqual(getNextFoldLine(5, foldingModel), null);
+			assert.strictEqual(getNextFoldLine(6, foldingModel), null);
+
+			// Test jump to previous.
+			assert.strictEqual(getPreviousFoldLine(1, foldingModel), null);
+			assert.strictEqual(getPreviousFoldLine(2, foldingModel), null);
+			assert.strictEqual(getPreviousFoldLine(3, foldingModel), 2);
+			assert.strictEqual(getPreviousFoldLine(4, foldingModel), 2);
+			assert.strictEqual(getPreviousFoldLine(5, foldingModel), 4);
+			assert.strictEqual(getPreviousFoldLine(6, foldingModel), 4);
+		} finally {
+			textModel.dispose();
+		}
+	});
 });