Skip to content

Commit 62f7c3d

Browse files
committed
feat(editor): add option to insert newline when folding last line
Introduce a new editor option `foldingAddNewlineAtEnd` that controls whether a newline character is added when folding the last line of a file. This option defaults to true, ensuring that a newline is inserted to maintain a clean separation at the end of the file when the last line is folded. The implementation includes updates to the folding model to check for this option and conditionally insert a newline. Additionally, tests have been added to verify the behavior of this new feature under various scenarios, including when the option is disabled or when the last line is already empty.
1 parent 4f24fc8 commit 62f7c3d

File tree

7 files changed

+440
-252
lines changed

7 files changed

+440
-252
lines changed

src/vs/editor/common/config/editorOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,11 @@ export interface IEditorOptions {
692692
* Defaults to 5000.
693693
*/
694694
foldingMaximumRegions?: number;
695+
/**
696+
* Controls whether to add a new line character when folding the last line of a file.
697+
* Defaults to true.
698+
*/
699+
foldingAddNewlineAtEnd?: boolean;
695700
/**
696701
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
697702
* Defaults to 'mouseover'.
@@ -5780,6 +5785,7 @@ export const enum EditorOption {
57805785
foldingHighlight,
57815786
foldingImportsByDefault,
57825787
foldingMaximumRegions,
5788+
foldingAddNewlineAtEnd,
57835789
unfoldOnClickAfterEndOfLine,
57845790
fontFamily,
57855791
fontInfo,
@@ -6276,6 +6282,10 @@ export const EditorOptions = {
62766282
5000, 10, 65000, // limit must be less than foldingRanges MAX_FOLDING_REGIONS
62776283
{ description: nls.localize('foldingMaximumRegions', "The maximum number of foldable regions. Increasing this value may result in the editor becoming less responsive when the current source has a large number of foldable regions.") }
62786284
)),
6285+
foldingAddNewlineAtEnd: register(new EditorBooleanOption(
6286+
EditorOption.foldingAddNewlineAtEnd, 'foldingAddNewlineAtEnd', true,
6287+
{ description: nls.localize('foldingAddNewlineAtEnd', "Controls whether a new line character is added when folding the last line of a file.") }
6288+
)),
62796289
unfoldOnClickAfterEndOfLine: register(new EditorBooleanOption(
62806290
EditorOption.unfoldOnClickAfterEndOfLine, 'unfoldOnClickAfterEndOfLine', false,
62816291
{ description: nls.localize('unfoldOnClickAfterEndOfLine', "Controls whether clicking on the empty content after a folded line will unfold the line.") }

src/vs/editor/common/standalone/standaloneEnums.ts

Lines changed: 117 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -231,122 +231,123 @@ export enum EditorOption {
231231
foldingHighlight = 54,
232232
foldingImportsByDefault = 55,
233233
foldingMaximumRegions = 56,
234-
unfoldOnClickAfterEndOfLine = 57,
235-
fontFamily = 58,
236-
fontInfo = 59,
237-
fontLigatures = 60,
238-
fontSize = 61,
239-
fontWeight = 62,
240-
fontVariations = 63,
241-
formatOnPaste = 64,
242-
formatOnType = 65,
243-
glyphMargin = 66,
244-
gotoLocation = 67,
245-
hideCursorInOverviewRuler = 68,
246-
hover = 69,
247-
inDiffEditor = 70,
248-
inlineSuggest = 71,
249-
letterSpacing = 72,
250-
lightbulb = 73,
251-
lineDecorationsWidth = 74,
252-
lineHeight = 75,
253-
lineNumbers = 76,
254-
lineNumbersMinChars = 77,
255-
linkedEditing = 78,
256-
links = 79,
257-
matchBrackets = 80,
258-
minimap = 81,
259-
mouseStyle = 82,
260-
mouseWheelScrollSensitivity = 83,
261-
mouseWheelZoom = 84,
262-
multiCursorMergeOverlapping = 85,
263-
multiCursorModifier = 86,
264-
mouseMiddleClickAction = 87,
265-
multiCursorPaste = 88,
266-
multiCursorLimit = 89,
267-
occurrencesHighlight = 90,
268-
occurrencesHighlightDelay = 91,
269-
overtypeCursorStyle = 92,
270-
overtypeOnPaste = 93,
271-
overviewRulerBorder = 94,
272-
overviewRulerLanes = 95,
273-
padding = 96,
274-
pasteAs = 97,
275-
parameterHints = 98,
276-
peekWidgetDefaultFocus = 99,
277-
placeholder = 100,
278-
definitionLinkOpensInPeek = 101,
279-
quickSuggestions = 102,
280-
quickSuggestionsDelay = 103,
281-
readOnly = 104,
282-
readOnlyMessage = 105,
283-
renameOnType = 106,
284-
renderRichScreenReaderContent = 107,
285-
renderControlCharacters = 108,
286-
renderFinalNewline = 109,
287-
renderLineHighlight = 110,
288-
renderLineHighlightOnlyWhenFocus = 111,
289-
renderValidationDecorations = 112,
290-
renderWhitespace = 113,
291-
revealHorizontalRightPadding = 114,
292-
roundedSelection = 115,
293-
rulers = 116,
294-
scrollbar = 117,
295-
scrollBeyondLastColumn = 118,
296-
scrollBeyondLastLine = 119,
297-
scrollPredominantAxis = 120,
298-
selectionClipboard = 121,
299-
selectionHighlight = 122,
300-
selectionHighlightMaxLength = 123,
301-
selectionHighlightMultiline = 124,
302-
selectOnLineNumbers = 125,
303-
showFoldingControls = 126,
304-
showUnused = 127,
305-
snippetSuggestions = 128,
306-
smartSelect = 129,
307-
smoothScrolling = 130,
308-
stickyScroll = 131,
309-
stickyTabStops = 132,
310-
stopRenderingLineAfter = 133,
311-
suggest = 134,
312-
suggestFontSize = 135,
313-
suggestLineHeight = 136,
314-
suggestOnTriggerCharacters = 137,
315-
suggestSelection = 138,
316-
tabCompletion = 139,
317-
tabIndex = 140,
318-
trimWhitespaceOnDelete = 141,
319-
unicodeHighlighting = 142,
320-
unusualLineTerminators = 143,
321-
useShadowDOM = 144,
322-
useTabStops = 145,
323-
wordBreak = 146,
324-
wordSegmenterLocales = 147,
325-
wordSeparators = 148,
326-
wordWrap = 149,
327-
wordWrapBreakAfterCharacters = 150,
328-
wordWrapBreakBeforeCharacters = 151,
329-
wordWrapColumn = 152,
330-
wordWrapOverride1 = 153,
331-
wordWrapOverride2 = 154,
332-
wrappingIndent = 155,
333-
wrappingStrategy = 156,
334-
showDeprecated = 157,
335-
inertialScroll = 158,
336-
inlayHints = 159,
337-
wrapOnEscapedLineFeeds = 160,
338-
effectiveCursorStyle = 161,
339-
editorClassName = 162,
340-
pixelRatio = 163,
341-
tabFocusMode = 164,
342-
layoutInfo = 165,
343-
wrappingInfo = 166,
344-
defaultColorDecorators = 167,
345-
colorDecoratorsActivatedOn = 168,
346-
inlineCompletionsAccessibilityVerbose = 169,
347-
effectiveEditContext = 170,
348-
scrollOnMiddleClick = 171,
349-
effectiveAllowVariableFonts = 172
234+
foldingAddNewlineAtEnd = 57,
235+
unfoldOnClickAfterEndOfLine = 58,
236+
fontFamily = 59,
237+
fontInfo = 60,
238+
fontLigatures = 61,
239+
fontSize = 62,
240+
fontWeight = 63,
241+
fontVariations = 64,
242+
formatOnPaste = 65,
243+
formatOnType = 66,
244+
glyphMargin = 67,
245+
gotoLocation = 68,
246+
hideCursorInOverviewRuler = 69,
247+
hover = 70,
248+
inDiffEditor = 71,
249+
inlineSuggest = 72,
250+
letterSpacing = 73,
251+
lightbulb = 74,
252+
lineDecorationsWidth = 75,
253+
lineHeight = 76,
254+
lineNumbers = 77,
255+
lineNumbersMinChars = 78,
256+
linkedEditing = 79,
257+
links = 80,
258+
matchBrackets = 81,
259+
minimap = 82,
260+
mouseStyle = 83,
261+
mouseWheelScrollSensitivity = 84,
262+
mouseWheelZoom = 85,
263+
multiCursorMergeOverlapping = 86,
264+
multiCursorModifier = 87,
265+
mouseMiddleClickAction = 88,
266+
multiCursorPaste = 89,
267+
multiCursorLimit = 90,
268+
occurrencesHighlight = 91,
269+
occurrencesHighlightDelay = 92,
270+
overtypeCursorStyle = 93,
271+
overtypeOnPaste = 94,
272+
overviewRulerBorder = 95,
273+
overviewRulerLanes = 96,
274+
padding = 97,
275+
pasteAs = 98,
276+
parameterHints = 99,
277+
peekWidgetDefaultFocus = 100,
278+
placeholder = 101,
279+
definitionLinkOpensInPeek = 102,
280+
quickSuggestions = 103,
281+
quickSuggestionsDelay = 104,
282+
readOnly = 105,
283+
readOnlyMessage = 106,
284+
renameOnType = 107,
285+
renderRichScreenReaderContent = 108,
286+
renderControlCharacters = 109,
287+
renderFinalNewline = 110,
288+
renderLineHighlight = 111,
289+
renderLineHighlightOnlyWhenFocus = 112,
290+
renderValidationDecorations = 113,
291+
renderWhitespace = 114,
292+
revealHorizontalRightPadding = 115,
293+
roundedSelection = 116,
294+
rulers = 117,
295+
scrollbar = 118,
296+
scrollBeyondLastColumn = 119,
297+
scrollBeyondLastLine = 120,
298+
scrollPredominantAxis = 121,
299+
selectionClipboard = 122,
300+
selectionHighlight = 123,
301+
selectionHighlightMaxLength = 124,
302+
selectionHighlightMultiline = 125,
303+
selectOnLineNumbers = 126,
304+
showFoldingControls = 127,
305+
showUnused = 128,
306+
snippetSuggestions = 129,
307+
smartSelect = 130,
308+
smoothScrolling = 131,
309+
stickyScroll = 132,
310+
stickyTabStops = 133,
311+
stopRenderingLineAfter = 134,
312+
suggest = 135,
313+
suggestFontSize = 136,
314+
suggestLineHeight = 137,
315+
suggestOnTriggerCharacters = 138,
316+
suggestSelection = 139,
317+
tabCompletion = 140,
318+
tabIndex = 141,
319+
trimWhitespaceOnDelete = 142,
320+
unicodeHighlighting = 143,
321+
unusualLineTerminators = 144,
322+
useShadowDOM = 145,
323+
useTabStops = 146,
324+
wordBreak = 147,
325+
wordSegmenterLocales = 148,
326+
wordSeparators = 149,
327+
wordWrap = 150,
328+
wordWrapBreakAfterCharacters = 151,
329+
wordWrapBreakBeforeCharacters = 152,
330+
wordWrapColumn = 153,
331+
wordWrapOverride1 = 154,
332+
wordWrapOverride2 = 155,
333+
wrappingIndent = 156,
334+
wrappingStrategy = 157,
335+
showDeprecated = 158,
336+
inertialScroll = 159,
337+
inlayHints = 160,
338+
wrapOnEscapedLineFeeds = 161,
339+
effectiveCursorStyle = 162,
340+
editorClassName = 163,
341+
pixelRatio = 164,
342+
tabFocusMode = 165,
343+
layoutInfo = 166,
344+
wrappingInfo = 167,
345+
defaultColorDecorators = 168,
346+
colorDecoratorsActivatedOn = 169,
347+
inlineCompletionsAccessibilityVerbose = 170,
348+
effectiveEditContext = 171,
349+
scrollOnMiddleClick = 172,
350+
effectiveAllowVariableFonts = 173
350351
}
351352

352353
/**

src/vs/editor/contrib/folding/browser/folding.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export class FoldingController extends Disposable implements IEditorContribution
234234
}
235235

236236
this._currentModelHasFoldedImports = false;
237-
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
237+
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider, this.editor);
238238
this.localToDispose.add(this.foldingModel);
239239

240240
this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);

src/vs/editor/contrib/folding/browser/foldingModel.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55

66
import { Emitter, Event } from '../../../../base/common/event.js';
77
import { IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from '../../../common/model.js';
8+
import { ICodeEditor } from '../../../browser/editorBrowser.js';
89
import { FoldingRegion, FoldingRegions, ILineRange, FoldRange, FoldSource } from './foldingRanges.js';
910
import { hash } from '../../../../base/common/hash.js';
1011
import { SelectedLines } from './folding.js';
12+
import { EditorOption } from '../../../common/config/editorOptions.js';
1113

1214
export interface IDecorationProvider {
1315
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions;
@@ -31,6 +33,7 @@ export type CollapseMemento = ILineMemento[];
3133
export class FoldingModel {
3234
private readonly _textModel: ITextModel;
3335
private readonly _decorationProvider: IDecorationProvider;
36+
private readonly editor: ICodeEditor;
3437

3538
private _regions: FoldingRegions;
3639
private _editorDecorationIds: string[];
@@ -42,11 +45,12 @@ export class FoldingModel {
4245
public get textModel() { return this._textModel; }
4346
public get decorationProvider() { return this._decorationProvider; }
4447

45-
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) {
48+
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider, editor: ICodeEditor) {
4649
this._textModel = textModel;
4750
this._decorationProvider = decorationProvider;
4851
this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0));
4952
this._editorDecorationIds = [];
53+
this.editor = editor;
5054
}
5155

5256
public toggleCollapseState(toggledRegions: FoldingRegion[]) {
@@ -60,6 +64,7 @@ export class FoldingModel {
6064
let k = 0; // index from [0 ... this.regions.length]
6165
let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated
6266
let lastHiddenLine = -1; // the end of the last hidden lines
67+
let isNewlineNeededAtEnd: boolean = false;
6368
const updateDecorationsUntil = (index: number) => {
6469
while (k < index) {
6570
const endLineNumber = this._regions.getEndLineNumber(k);
@@ -86,8 +91,33 @@ export class FoldingModel {
8691
this._regions.setCollapsed(index, newCollapseState);
8792

8893
dirtyRegionEndLine = Math.max(dirtyRegionEndLine, this._regions.getEndLineNumber(index));
94+
95+
// If isNewlineNeededAtEnd is already true, we can skip this check
96+
if (!isNewlineNeededAtEnd && this.editor.getOption(EditorOption.foldingAddNewlineAtEnd) && newCollapseState && dirtyRegionEndLine === this._textModel.getLineCount()) {
97+
const lastLineContent = this._textModel.getLineContent(dirtyRegionEndLine);
98+
// Only insert a newline if the last line is not already empty
99+
if (lastLineContent.trim().length > 0) {
100+
this._textModel.pushEditOperations(
101+
null,
102+
[{
103+
range: {
104+
startLineNumber: dirtyRegionEndLine,
105+
startColumn: lastLineContent.length + 1,
106+
endLineNumber: dirtyRegionEndLine,
107+
endColumn: lastLineContent.length + 1,
108+
},
109+
text: this._textModel.getEOL(),
110+
forceMoveMarkers: true
111+
}],
112+
() => null
113+
);
114+
}
115+
}
116+
isNewlineNeededAtEnd = true;
89117
}
90118
}
119+
120+
91121
updateDecorationsUntil(this._regions.length);
92122
});
93123
this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions });

0 commit comments

Comments
 (0)