Merge pull request #100027 from matthew-haines/master

#97684 Add alt-click & actions to fold/unfold surronding regions
This commit is contained in:
Martin Aeschlimann 2021-03-19 10:17:01 +01:00 committed by GitHub
commit c17a5ca80f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 137 additions and 9 deletions

View file

@ -14,7 +14,7 @@ import { ScrollType, IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, registerInstantiatedEditorAction } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateForType, toggleCollapseState, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel';
import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateForType, setCollapseStateForRest, toggleCollapseState, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel';
import { FoldingDecorationProvider, foldingCollapsedIcon, foldingExpandedIcon } from './foldingDecorations';
import { FoldingRegions, FoldingRegion } from './foldingRanges';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
@ -429,18 +429,34 @@ export class FoldingController extends Disposable implements IEditorContribution
if (region && region.startLineNumber === lineNumber) {
let isCollapsed = region.isCollapsed;
if (iconClicked || isCollapsed) {
let surrounding = e.event.altKey;
let toToggle = [];
let recursive = e.event.middleButton || e.event.shiftKey;
if (recursive) {
for (const r of foldingModel.getRegionsInside(region)) {
if (r.isCollapsed === isCollapsed) {
if (surrounding) {
let filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region!) && !region!.containedBy(otherRegion);
let toMaybeToggle = foldingModel.getRegionsInside(null, filter);
for (const r of toMaybeToggle) {
if (r.isCollapsed) {
toToggle.push(r);
}
}
// if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding
if (toToggle.length === 0) {
toToggle = toMaybeToggle;
}
}
// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
if (isCollapsed || !recursive || toToggle.length === 0) {
toToggle.push(region);
else {
let recursive = e.event.middleButton || e.event.shiftKey;
if (recursive) {
for (const r of foldingModel.getRegionsInside(region)) {
if (r.isCollapsed === isCollapsed) {
toToggle.push(r);
}
}
}
// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
if (isCollapsed || !recursive || toToggle.length === 0) {
toToggle.push(region);
}
}
foldingModel.toggleCollapseState(toToggle);
this.reveal({ lineNumber, column: 1 });
@ -821,6 +837,51 @@ class UnfoldAllRegionsAction extends FoldingAction<void> {
}
}
class FoldAllRegionsExceptAction extends FoldingAction<void> {
constructor() {
super({
id: 'editor.foldAllExcept',
label: nls.localize('foldAllExcept.label', "Fold All Regions Except Selected"),
alias: 'Fold All Regions Except Selected',
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_MINUS),
weight: KeybindingWeight.EditorContrib
}
});
}
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
let selectedLines = this.getSelectedLines(editor);
setCollapseStateForRest(foldingModel, true, selectedLines);
}
}
class UnfoldAllRegionsExceptAction extends FoldingAction<void> {
constructor() {
super({
id: 'editor.unfoldAllExcept',
label: nls.localize('unfoldAllExcept.label', "Unfold All Regions Except Selected"),
alias: 'Unfold All Regions Except Selected',
precondition: CONTEXT_FOLDING_ENABLED,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_EQUAL),
weight: KeybindingWeight.EditorContrib
}
});
}
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
let selectedLines = this.getSelectedLines(editor);
setCollapseStateForRest(foldingModel, false, selectedLines);
}
}
class FoldAllAction extends FoldingAction<void> {
constructor() {
@ -886,6 +947,8 @@ registerEditorAction(UnfoldAllAction);
registerEditorAction(FoldAllBlockCommentsAction);
registerEditorAction(FoldAllRegionsAction);
registerEditorAction(UnfoldAllRegionsAction);
registerEditorAction(FoldAllRegionsExceptAction);
registerEditorAction(UnfoldAllRegionsExceptAction);
registerEditorAction(ToggleFoldAction);
for (let i = 1; i <= 7; i++) {

View file

@ -368,6 +368,21 @@ export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: n
foldingModel.toggleCollapseState(toToggle);
}
/**
* Folds or unfolds all regions, except if they contain or are contained by a region of one of the blocked lines.
* @param doCollapse Whether to collapse or expand
* @param blockedLineNumbers the location of regions to not collapse or expand
*/
export function setCollapseStateForRest(foldingModel: FoldingModel, doCollapse: boolean, blockedLineNumbers: number[]): void {
let filteredRegions: FoldingRegion[] = [];
for (let lineNumber of blockedLineNumbers) {
filteredRegions.push(foldingModel.getAllRegionsAtLine(lineNumber, undefined)[0]);
}
let filter = (region: FoldingRegion) => filteredRegions.every((filteredRegion) => !filteredRegion.containedBy(region) && !region.containedBy(filteredRegion)) && region.isCollapsed !== doCollapse;
let toToggle = foldingModel.getRegionsInside(null, filter);
foldingModel.toggleCollapseState(toToggle);
}
/**
* Folds all regions for which the lines start with a given regex
* @param foldingModel the folding model

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel';
import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateUp, setCollapseStateForRest } from 'vs/editor/contrib/folding/foldingModel';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider';
@ -717,6 +717,56 @@ suite('Folding Model', () => {
});
test('setCollapseStateForRest', () => {
let lines = [
/* 1*/ '//#region',
/* 2*/ '//#endregion',
/* 3*/ 'class A {',
/* 4*/ ' void foo() {',
/* 5*/ ' if (true) {',
/* 6*/ ' return;',
/* 7*/ ' }',
/* 8*/ '',
/* 9*/ ' if (true) {',
/* 10*/ ' return;',
/* 11*/ ' }',
/* 12*/ ' }',
/* 13*/ '}'];
let textModel = createTextModel(lines.join('\n'));
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
foldingModel.update(ranges);
let r1 = r(1, 2, false);
let r2 = r(3, 12, false);
let r3 = r(4, 11, false);
let r4 = r(5, 6, false);
let r5 = r(9, 10, false);
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
setCollapseStateForRest(foldingModel, true, [5]);
assertFoldedRanges(foldingModel, [r1, r5], '1');
setCollapseStateForRest(foldingModel, false, [5]);
assertFoldedRanges(foldingModel, [], '2');
setCollapseStateForRest(foldingModel, true, [1]);
assertFoldedRanges(foldingModel, [r2, r3, r4, r5], '3');
setCollapseStateForRest(foldingModel, true, [3]);
assertFoldedRanges(foldingModel, [r1, r2, r3, r4, r5], '3');
} finally {
textModel.dispose();
}
});
test('folding decoration', () => {
let lines = [
/* 1*/ 'class A {',