Skip to content

Commit da71a35

Browse files
Make hidden fold ranges independent of range provider, add manual fol… (microsoft#139779)
* squash all changes for keepCollapsedFolds * change manual fold icon, auto unfold when del at end of first line Co-authored-by: Martin Aeschlimann <[email protected]>
1 parent 0dca5e1 commit da71a35

File tree

7 files changed

+504
-241
lines changed

7 files changed

+504
-241
lines changed

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

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua
2626
import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel';
2727
import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeModel';
2828
import { IndentRangeProvider } from 'vs/editor/contrib/folding/browser/indentRangeProvider';
29-
import { ID_INIT_PROVIDER, InitializingRangeProvider } from 'vs/editor/contrib/folding/browser/intializingRangeProvider';
3029
import * as nls from 'vs/nls';
3130
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
3231
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
3332
import { editorSelectionBackground, iconForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
3433
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
35-
import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon } from './foldingDecorations';
36-
import { FoldingRegion, FoldingRegions } from './foldingRanges';
37-
import { ID_SYNTAX_PROVIDER, SyntaxRangeProvider } from './syntaxRangeProvider';
34+
import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualIcon } from './foldingDecorations';
35+
import { FoldingRegion, FoldingRegions, FoldRange } from './foldingRanges';
36+
import { SyntaxRangeProvider } from './syntaxRangeProvider';
3837
import { INotificationService } from 'vs/platform/notification/common/notification';
3938
import Severity from 'vs/base/common/severity';
4039
import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
@@ -84,8 +83,6 @@ export class FoldingController extends Disposable implements IEditorContribution
8483
private rangeProvider: RangeProvider | null;
8584
private foldingRegionPromise: CancelablePromise<FoldingRegions | null> | null;
8685

87-
private foldingStateMemento: FoldingStateMemento | null;
88-
8986
private foldingModelPromise: Promise<FoldingModel | null> | null;
9087
private updateScheduler: Delayer<FoldingModel | null> | null;
9188
private readonly updateDebounceInfo: IFeatureDebounceInformation;
@@ -120,7 +117,6 @@ export class FoldingController extends Disposable implements IEditorContribution
120117
this.hiddenRangeModel = null;
121118
this.rangeProvider = null;
122119
this.foldingRegionPromise = null;
123-
this.foldingStateMemento = null;
124120
this.foldingModelPromise = null;
125121
this.updateScheduler = null;
126122
this.cursorChangedScheduler = null;
@@ -186,7 +182,7 @@ export class FoldingController extends Disposable implements IEditorContribution
186182
return {};
187183
}
188184
if (this.foldingModel) { // disposed ?
189-
const collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel!.getMemento();
185+
const collapsedRegions = this.foldingModel.getMemento();
190186
const provider = this.rangeProvider ? this.rangeProvider.id : undefined;
191187
return { collapsedRegions, lineCount: model.getLineCount(), provider, foldedImports: this._currentModelHasFoldedImports };
192188
}
@@ -206,29 +202,12 @@ export class FoldingController extends Disposable implements IEditorContribution
206202
}
207203

208204
this._currentModelHasFoldedImports = !!state.foldedImports;
209-
if (!state.collapsedRegions) {
210-
return;
211-
}
212-
213-
if (state.provider === ID_SYNTAX_PROVIDER || state.provider === ID_INIT_PROVIDER) {
214-
this.foldingStateMemento = state;
215-
}
216-
217-
const collapsedRegions = state.collapsedRegions;
218-
// set the hidden ranges right away, before waiting for the folding model.
219-
if (this.hiddenRangeModel.applyMemento(collapsedRegions)) {
220-
const foldingModel = this.getFoldingModel();
221-
if (foldingModel) {
222-
foldingModel.then(foldingModel => {
223-
if (foldingModel) {
224-
this._restoringViewState = true;
225-
try {
226-
foldingModel.applyMemento(collapsedRegions);
227-
} finally {
228-
this._restoringViewState = false;
229-
}
230-
}
231-
}).then(undefined, onUnexpectedError);
205+
if (state.collapsedRegions && state.collapsedRegions.length > 0 && this.foldingModel) {
206+
this._restoringViewState = true;
207+
try {
208+
this.foldingModel.applyMemento(state.collapsedRegions!);
209+
} finally {
210+
this._restoringViewState = false;
232211
}
233212
}
234213
}
@@ -243,7 +222,7 @@ export class FoldingController extends Disposable implements IEditorContribution
243222
}
244223

245224
this._currentModelHasFoldedImports = false;
246-
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
225+
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider, this.triggerFoldingModelChanged.bind(this));
247226
this.localToDispose.add(this.foldingModel);
248227

249228
this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);
@@ -274,7 +253,6 @@ export class FoldingController extends Disposable implements IEditorContribution
274253
this.foldingModelPromise = null;
275254
this.hiddenRangeModel = null;
276255
this.cursorChangedScheduler = null;
277-
this.foldingStateMemento = null;
278256
if (this.rangeProvider) {
279257
this.rangeProvider.dispose();
280258
}
@@ -297,21 +275,12 @@ export class FoldingController extends Disposable implements IEditorContribution
297275
return this.rangeProvider;
298276
}
299277
this.rangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._maxFoldingRegions); // fallback
300-
301278
if (this._useFoldingProviders && this.foldingModel) {
302279
const foldingProviders = this.languageFeaturesService.foldingRangeProvider.ordered(this.foldingModel.textModel);
303-
if (foldingProviders.length === 0 && this.foldingStateMemento && this.foldingStateMemento.collapsedRegions) {
304-
const rangeProvider = this.rangeProvider = new InitializingRangeProvider(editorModel, this.foldingStateMemento.collapsedRegions, () => {
305-
// if after 30 the InitializingRangeProvider is still not replaced, force a refresh
306-
this.foldingStateMemento = null;
307-
this.onFoldingStrategyChanged();
308-
}, 30000);
309-
return rangeProvider; // keep memento in case there are still no foldingProviders on the next request.
310-
} else if (foldingProviders.length > 0) {
280+
if (foldingProviders.length > 0) {
311281
this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.triggerFoldingModelChanged(), this._maxFoldingRegions);
312282
}
313283
}
314-
this.foldingStateMemento = null;
315284
return this.rangeProvider;
316285
}
317286

@@ -1097,6 +1066,59 @@ class GotoNextFoldAction extends FoldingAction<void> {
10971066
}
10981067
}
10991068

1069+
class FoldSelectedAction extends FoldingAction<void> {
1070+
1071+
constructor() {
1072+
super({
1073+
id: 'editor.foldSelected',
1074+
label: nls.localize('foldSelectedAction.label', "Fold Selected Lines"),
1075+
alias: 'Fold Selected Lines',
1076+
precondition: CONTEXT_FOLDING_ENABLED,
1077+
kbOpts: {
1078+
kbExpr: EditorContextKeys.editorTextFocus,
1079+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period),
1080+
weight: KeybindingWeight.EditorContrib
1081+
}
1082+
});
1083+
}
1084+
1085+
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1086+
const collapseRanges: FoldRange[] = [];
1087+
const selections = editor.getSelections();
1088+
if (selections) {
1089+
for (const selection of selections) {
1090+
let endLineNumber = selection.endLineNumber;
1091+
if (selection.endColumn === 1) {
1092+
--endLineNumber;
1093+
}
1094+
if (endLineNumber > selection.startLineNumber) {
1095+
collapseRanges.push(<FoldRange>{
1096+
startLineNumber: selection.startLineNumber,
1097+
endLineNumber: endLineNumber,
1098+
type: undefined,
1099+
isCollapsed: true,
1100+
isManualSelection: true
1101+
});
1102+
editor.setSelection({
1103+
startLineNumber: selection.startLineNumber,
1104+
startColumn: 1,
1105+
endLineNumber: selection.startLineNumber,
1106+
endColumn: 1
1107+
});
1108+
}
1109+
}
1110+
if (collapseRanges.length > 0) {
1111+
collapseRanges.sort((a, b) => {
1112+
return a.startLineNumber - b.startLineNumber;
1113+
});
1114+
const newRanges = FoldingRegions.sanitizeAndMerge(foldingModel.regions, collapseRanges, editor.getModel()?.getLineCount());
1115+
foldingModel.updatePost(FoldingRegions.fromFoldRanges(newRanges));
1116+
}
1117+
}
1118+
}
1119+
}
1120+
1121+
11001122
registerEditorContribution(FoldingController.ID, FoldingController);
11011123
registerEditorAction(UnfoldAction);
11021124
registerEditorAction(UnFoldRecursivelyAction);
@@ -1113,6 +1135,7 @@ registerEditorAction(ToggleFoldAction);
11131135
registerEditorAction(GotoParentFoldAction);
11141136
registerEditorAction(GotoPreviousFoldAction);
11151137
registerEditorAction(GotoNextFoldAction);
1138+
registerEditorAction(FoldSelectedAction);
11161139

11171140
for (let i = 1; i <= 7; i++) {
11181141
registerInstantiatedEditorAction(
@@ -1143,7 +1166,8 @@ registerThemingParticipant((theme, collector) => {
11431166
if (editorFoldColor) {
11441167
collector.addRule(`
11451168
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)},
1146-
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)} {
1169+
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)},
1170+
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualIcon)} {
11471171
color: ${editorFoldColor} !important;
11481172
}
11491173
`);

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Codicon } from 'vs/base/common/codicons';
77
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
8-
import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model';
8+
import { IModelDecorationOptions, IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model';
99
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
1010
import { IDecorationProvider } from 'vs/editor/contrib/folding/browser/foldingModel';
1111
import { localize } from 'vs/nls';
@@ -14,6 +14,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
1414

1515
export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.'));
1616
export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.'));
17+
export const foldingManualIcon = registerIcon('folding-manual', Codicon.ellipsis, localize('foldingManualIcon', 'Icon for manually collapsed ranges in the editor glyph margin.'));
1718
export class FoldingDecorationProvider implements IDecorationProvider {
1819

1920
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
@@ -33,6 +34,23 @@ export class FoldingDecorationProvider implements IDecorationProvider {
3334
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
3435
});
3536

37+
private static readonly MANUALLY_COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
38+
description: 'folding-manually-collapsed-visual-decoration',
39+
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
40+
afterContentClassName: 'inline-folded',
41+
isWholeLine: true,
42+
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
43+
});
44+
45+
private static readonly MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
46+
description: 'folding-manually-collapsed-highlighted-visual-decoration',
47+
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
48+
afterContentClassName: 'inline-folded',
49+
className: 'folded-background',
50+
isWholeLine: true,
51+
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
52+
});
53+
3654
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
3755
description: 'folding-expanded-auto-hide-visual-decoration',
3856
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
@@ -59,12 +77,15 @@ export class FoldingDecorationProvider implements IDecorationProvider {
5977
constructor(private readonly editor: ICodeEditor) {
6078
}
6179

62-
getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions {
63-
if (isHidden || this.showFoldingControls === 'never') {
80+
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions {
81+
if (isHidden // is inside another collapsed region
82+
|| this.showFoldingControls === 'never' || (isManualSelection && !isCollapsed)) { //
6483
return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION;
6584
}
6685
if (isCollapsed) {
67-
return this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION;
86+
return isManualSelection ?
87+
(this.showFoldingHighlights ? FoldingDecorationProvider.MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.MANUALLY_COLLAPSED_VISUAL_DECORATION)
88+
: (this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION);
6889
} else if (this.showFoldingControls === 'mouseover') {
6990
return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION;
7091
} else {

0 commit comments

Comments
 (0)