mirror of
https://github.com/Microsoft/vscode
synced 2024-10-30 11:10:48 +00:00
simplify fold merging (#155302)
* tune _trySanitizeAndMerge * more simplifications * maually expanded icon * folding ranges css * also persist manual expanded ranges * rename isManualSelection -> isManual * isUserDefined & isRecovered * Remove Manual Folding Ranges command
This commit is contained in:
parent
b2d1545f15
commit
c47c24af30
6 changed files with 279 additions and 211 deletions
|
@ -2,7 +2,8 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .margin-view-overlays .codicon-folding-manual-collapsed,
|
||||
.monaco-editor .margin-view-overlays .codicon-folding-manual-expanded,
|
||||
.monaco-editor .margin-view-overlays .codicon-folding-expanded,
|
||||
.monaco-editor .margin-view-overlays .codicon-folding-collapsed {
|
||||
cursor: pointer;
|
||||
|
@ -17,6 +18,7 @@
|
|||
|
||||
.monaco-editor .margin-view-overlays:hover .codicon,
|
||||
.monaco-editor .margin-view-overlays .codicon.codicon-folding-collapsed,
|
||||
.monaco-editor .margin-view-overlays .codicon.codicon-folding-manual-collapsed,
|
||||
.monaco-editor .margin-view-overlays .codicon.alwaysShowFoldIcons {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -29,3 +31,7 @@
|
|||
line-height: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-editor .margin-view-overlays .codicon.codicon-folding-manual-expanded::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont
|
|||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { editorSelectionBackground, iconForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualIcon } from './foldingDecorations';
|
||||
import { FoldingRegion, FoldingRegions, FoldRange } from './foldingRanges';
|
||||
import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualCollapsedIcon, foldingManualExpandedIcon } from './foldingDecorations';
|
||||
import { FoldingRegion, FoldingRegions, FoldRange, ILineRange } from './foldingRanges';
|
||||
import { SyntaxRangeProvider } from './syntaxRangeProvider';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
@ -222,7 +222,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
|||
}
|
||||
|
||||
this._currentModelHasFoldedImports = false;
|
||||
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider, this.triggerFoldingModelChanged.bind(this));
|
||||
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
|
||||
this.localToDispose.add(this.foldingModel);
|
||||
|
||||
this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);
|
||||
|
@ -293,7 +293,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
|||
this.triggerFoldingModelChanged();
|
||||
}
|
||||
|
||||
private triggerFoldingModelChanged() {
|
||||
public triggerFoldingModelChanged() {
|
||||
if (this.updateScheduler) {
|
||||
if (this.foldingRegionPromise) {
|
||||
this.foldingRegionPromise.cancel();
|
||||
|
@ -1066,13 +1066,13 @@ class GotoNextFoldAction extends FoldingAction<void> {
|
|||
}
|
||||
}
|
||||
|
||||
class FoldSelectedAction extends FoldingAction<void> {
|
||||
class FoldRangeFromSelectionAction extends FoldingAction<void> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.foldSelected',
|
||||
label: nls.localize('foldSelectedAction.label', "Fold Selected Lines"),
|
||||
alias: 'Fold Selected Lines',
|
||||
id: 'editor.createFoldingRangeFromSelection',
|
||||
label: nls.localize('createManualFoldRange.label', "Create Manual Folding Range from Selection"),
|
||||
alias: 'Create Folding Range from Selection',
|
||||
precondition: CONTEXT_FOLDING_ENABLED,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
|
@ -1097,7 +1097,8 @@ class FoldSelectedAction extends FoldingAction<void> {
|
|||
endLineNumber: endLineNumber,
|
||||
type: undefined,
|
||||
isCollapsed: true,
|
||||
isManualSelection: true
|
||||
isUserDefined: true,
|
||||
isRecovered: false
|
||||
});
|
||||
editor.setSelection({
|
||||
startLineNumber: selection.startLineNumber,
|
||||
|
@ -1118,6 +1119,40 @@ class FoldSelectedAction extends FoldingAction<void> {
|
|||
}
|
||||
}
|
||||
|
||||
class RemoveFoldRangeFromSelectionAction extends FoldingAction<void> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.removeManualFoldingRanges',
|
||||
label: nls.localize('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"),
|
||||
alias: 'Remove Manual Folding Ranges',
|
||||
precondition: CONTEXT_FOLDING_ENABLED,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
|
||||
const selections = editor.getSelections();
|
||||
if (selections) {
|
||||
const ranges: ILineRange[] = [];
|
||||
for (const selection of selections) {
|
||||
let endLineNumber = selection.endLineNumber;
|
||||
if (selection.endColumn === 1) {
|
||||
--endLineNumber;
|
||||
}
|
||||
const startLineNumber = selection.startLineNumber;
|
||||
ranges.push(endLineNumber >= selection.startLineNumber ? { startLineNumber, endLineNumber } : { endLineNumber, startLineNumber });
|
||||
}
|
||||
foldingModel.removeManualRanges(ranges);
|
||||
foldingController.triggerFoldingModelChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerEditorContribution(FoldingController.ID, FoldingController);
|
||||
registerEditorAction(UnfoldAction);
|
||||
|
@ -1135,7 +1170,8 @@ registerEditorAction(ToggleFoldAction);
|
|||
registerEditorAction(GotoParentFoldAction);
|
||||
registerEditorAction(GotoPreviousFoldAction);
|
||||
registerEditorAction(GotoNextFoldAction);
|
||||
registerEditorAction(FoldSelectedAction);
|
||||
registerEditorAction(FoldRangeFromSelectionAction);
|
||||
registerEditorAction(RemoveFoldRangeFromSelectionAction);
|
||||
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
registerInstantiatedEditorAction(
|
||||
|
@ -1167,7 +1203,8 @@ registerThemingParticipant((theme, collector) => {
|
|||
collector.addRule(`
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)},
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)},
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualIcon)} {
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualExpandedIcon)},
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualCollapsedIcon)} {
|
||||
color: ${editorFoldColor} !important;
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -14,12 +14,14 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
|||
|
||||
export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.'));
|
||||
export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.'));
|
||||
export const foldingManualIcon = registerIcon('folding-manual', Codicon.ellipsis, localize('foldingManualIcon', 'Icon for manually collapsed ranges in the editor glyph margin.'));
|
||||
export const foldingManualCollapsedIcon = registerIcon('folding-manual-collapsed', Codicon.ellipsis, localize('foldingManualCollapedIcon', 'Icon for manually collapsed ranges in the editor glyph margin.'));
|
||||
export const foldingManualExpandedIcon = registerIcon('folding-manual-expanded', Codicon.ellipsis, localize('foldingManualExpandedIcon', 'Icon for manually expanded ranges in the editor glyph margin.'));
|
||||
|
||||
export class FoldingDecorationProvider implements IDecorationProvider {
|
||||
|
||||
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-collapsed-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
|
||||
|
@ -27,7 +29,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
|
||||
private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-collapsed-highlighted-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
className: 'folded-background',
|
||||
isWholeLine: true,
|
||||
|
@ -36,19 +38,19 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
|
||||
private static readonly MANUALLY_COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-collapsed-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
});
|
||||
|
||||
private static readonly MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-collapsed-highlighted-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
className: 'folded-background',
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualCollapsedIcon)
|
||||
});
|
||||
|
||||
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
|
@ -65,6 +67,21 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
});
|
||||
|
||||
private static readonly MANUALLY_EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-expanded-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingManualExpandedIcon)
|
||||
});
|
||||
|
||||
private static readonly MANUALLY_EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-expanded-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualExpandedIcon)
|
||||
});
|
||||
|
||||
|
||||
private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-hidden-range-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
|
@ -77,19 +94,19 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
constructor(private readonly editor: ICodeEditor) {
|
||||
}
|
||||
|
||||
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions {
|
||||
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions {
|
||||
if (isHidden // is inside another collapsed region
|
||||
|| this.showFoldingControls === 'never' || (isManualSelection && !isCollapsed)) { //
|
||||
|| this.showFoldingControls === 'never') {
|
||||
return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION;
|
||||
}
|
||||
if (isCollapsed) {
|
||||
return isManualSelection ?
|
||||
return isManual ?
|
||||
(this.showFoldingHighlights ? FoldingDecorationProvider.MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.MANUALLY_COLLAPSED_VISUAL_DECORATION)
|
||||
: (this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION);
|
||||
} else if (this.showFoldingControls === 'mouseover') {
|
||||
return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION;
|
||||
return isManual ? FoldingDecorationProvider.MANUALLY_EXPANDED_AUTO_HIDE_VISUAL_DECORATION : FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION;
|
||||
} else {
|
||||
return FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION;
|
||||
return isManual ? FoldingDecorationProvider.MANUALLY_EXPANDED_VISUAL_DECORATION : FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FoldingRegion, FoldingRegions, ILineRange, FoldRange } from './foldingR
|
|||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
export interface IDecorationProvider {
|
||||
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions;
|
||||
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions;
|
||||
changeDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null;
|
||||
removeDecorations(decorationIds: string[]): void;
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ export interface FoldingModelChangeEvent {
|
|||
|
||||
interface ILineMemento extends ILineRange {
|
||||
checksum?: number;
|
||||
isCollapsed?: boolean;
|
||||
isUserDefined?: boolean;
|
||||
}
|
||||
|
||||
export type CollapseMemento = ILineMemento[];
|
||||
|
@ -28,7 +30,6 @@ export type CollapseMemento = ILineMemento[];
|
|||
export class FoldingModel {
|
||||
private readonly _textModel: ITextModel;
|
||||
private readonly _decorationProvider: IDecorationProvider;
|
||||
private readonly _triggerRecomputeRanges: (() => void) | undefined;
|
||||
|
||||
private _regions: FoldingRegions;
|
||||
private _editorDecorationIds: string[];
|
||||
|
@ -40,10 +41,9 @@ export class FoldingModel {
|
|||
public get textModel() { return this._textModel; }
|
||||
public get decorationProvider() { return this._decorationProvider; }
|
||||
|
||||
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider, triggerRecomputeRanges?: () => void) {
|
||||
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) {
|
||||
this._textModel = textModel;
|
||||
this._decorationProvider = decorationProvider;
|
||||
this._triggerRecomputeRanges = triggerRecomputeRanges;
|
||||
this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0));
|
||||
this._editorDecorationIds = [];
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ export class FoldingModel {
|
|||
toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex);
|
||||
|
||||
const processed: { [key: string]: boolean | undefined } = {};
|
||||
const manualExpanded = false;
|
||||
this._decorationProvider.changeDecorations(accessor => {
|
||||
let k = 0; // index from [0 ... this.regions.length]
|
||||
let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated
|
||||
|
@ -64,9 +63,9 @@ export class FoldingModel {
|
|||
while (k < index) {
|
||||
const endLineNumber = this._regions.getEndLineNumber(k);
|
||||
const isCollapsed = this._regions.isCollapsed(k);
|
||||
const isManualSelection = this.regions.isManualSelection(k);
|
||||
if (endLineNumber <= dirtyRegionEndLine) {
|
||||
accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection));
|
||||
const isManual = this.regions.isUserDefined(k) || this.regions.isRecovered(k);
|
||||
accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual));
|
||||
}
|
||||
if (isCollapsed && endLineNumber > lastHiddenLine) {
|
||||
lastHiddenLine = endLineNumber;
|
||||
|
@ -91,16 +90,30 @@ export class FoldingModel {
|
|||
updateDecorationsUntil(this._regions.length);
|
||||
});
|
||||
this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions });
|
||||
if (manualExpanded && this._triggerRecomputeRanges) {
|
||||
// expanding a range which didn't originate from range provider might now enable ranges
|
||||
// from the provider which were previously dropped due to the collapsed range
|
||||
this._triggerRecomputeRanges();
|
||||
}
|
||||
|
||||
public removeManualRanges(ranges: ILineRange[]) {
|
||||
const newFoldingRanges: FoldRange[] = new Array();
|
||||
const containedBy = (foldRange: FoldRange) => {
|
||||
for (const range of ranges) {
|
||||
if (range.startLineNumber <= foldRange.startLineNumber && range.endLineNumber >= foldRange.endLineNumber) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (let i = 0; i < this._regions.length; i++) {
|
||||
const foldRange = this._regions.toFoldRange(i);
|
||||
if (!foldRange.isUserDefined && !foldRange.isRecovered || !containedBy(foldRange)) {
|
||||
newFoldingRanges.push(foldRange);
|
||||
}
|
||||
}
|
||||
this.updatePost(FoldingRegions.fromFoldRanges(newFoldingRanges));
|
||||
}
|
||||
|
||||
public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void {
|
||||
const hiddenRanges = this._currentHiddenRegions(blockedLineNumers);
|
||||
const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, hiddenRanges, this._textModel.getLineCount());
|
||||
const foldedOrManualRanges = this._currentFoldedOrManualRanges(blockedLineNumers);
|
||||
const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, foldedOrManualRanges, this._textModel.getLineCount());
|
||||
this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
|
||||
}
|
||||
|
||||
|
@ -111,14 +124,14 @@ export class FoldingModel {
|
|||
const startLineNumber = newRegions.getStartLineNumber(index);
|
||||
const endLineNumber = newRegions.getEndLineNumber(index);
|
||||
const isCollapsed = newRegions.isCollapsed(index);
|
||||
const isManualSelection = newRegions.isManualSelection(index);
|
||||
const isManual = newRegions.isUserDefined(index) || newRegions.isRecovered(index);
|
||||
const decorationRange = {
|
||||
startLineNumber: startLineNumber,
|
||||
startColumn: this._textModel.getLineMaxColumn(startLineNumber),
|
||||
endLineNumber: endLineNumber,
|
||||
endColumn: this._textModel.getLineMaxColumn(endLineNumber) + 1
|
||||
};
|
||||
newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection) });
|
||||
newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual) });
|
||||
if (isCollapsed && endLineNumber > lastHiddenLine) {
|
||||
lastHiddenLine = endLineNumber;
|
||||
}
|
||||
|
@ -128,7 +141,7 @@ export class FoldingModel {
|
|||
this._updateEventEmitter.fire({ model: this });
|
||||
}
|
||||
|
||||
private _currentHiddenRegions(blockedLineNumers: number[] = []): FoldRange[] {
|
||||
private _currentFoldedOrManualRanges(blockedLineNumers: number[] = []): FoldRange[] {
|
||||
|
||||
const isBlocked = (startLineNumber: number, endLineNumber: number) => {
|
||||
for (const blockedLineNumber of blockedLineNumers) {
|
||||
|
@ -139,42 +152,47 @@ export class FoldingModel {
|
|||
return false;
|
||||
};
|
||||
|
||||
const hiddenRanges: FoldRange[] = [];
|
||||
const foldedRanges: FoldRange[] = [];
|
||||
for (let i = 0, limit = this._regions.length; i < limit; i++) {
|
||||
if (this.regions.isCollapsed(i)) {
|
||||
const hiddenRange = this._regions.toFoldRange(i);
|
||||
let isCollapsed = this.regions.isCollapsed(i);
|
||||
const isUserDefined = this.regions.isUserDefined(i);
|
||||
const isRecovered = this.regions.isRecovered(i);
|
||||
if (isCollapsed || isUserDefined || isRecovered) {
|
||||
const foldRange = this._regions.toFoldRange(i);
|
||||
const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[i]);
|
||||
if (decRange
|
||||
&& !isBlocked(decRange.startLineNumber, decRange.endLineNumber)
|
||||
// if not same length user has modified it, skip and auto-expand
|
||||
&& decRange.endLineNumber - decRange.startLineNumber
|
||||
=== hiddenRange.endLineNumber - hiddenRange.startLineNumber) {
|
||||
hiddenRanges.push({
|
||||
if (decRange) {
|
||||
if (isCollapsed && (isBlocked(decRange.startLineNumber, decRange.endLineNumber) || decRange.endLineNumber - decRange.startLineNumber !== foldRange.endLineNumber - foldRange.startLineNumber)) {
|
||||
isCollapsed = false; // uncollapse is the range is blocked or there has been lines removed or added
|
||||
}
|
||||
foldedRanges.push({
|
||||
startLineNumber: decRange.startLineNumber,
|
||||
endLineNumber: decRange.endLineNumber,
|
||||
type: hiddenRange.type,
|
||||
isCollapsed: true,
|
||||
isManualSelection: hiddenRange.isManualSelection
|
||||
type: foldRange.type,
|
||||
isCollapsed,
|
||||
isUserDefined,
|
||||
isRecovered
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hiddenRanges;
|
||||
return foldedRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse state memento, for persistence only
|
||||
*/
|
||||
public getMemento(): CollapseMemento | undefined {
|
||||
const hiddenRegions = this._currentHiddenRegions();
|
||||
const foldedOrManualRanges = this._currentFoldedOrManualRanges();
|
||||
const result: ILineMemento[] = [];
|
||||
for (let i = 0, limit = hiddenRegions.length; i < limit; i++) {
|
||||
const range = hiddenRegions[i];
|
||||
for (let i = 0, limit = foldedOrManualRanges.length; i < limit; i++) {
|
||||
const range = foldedOrManualRanges[i];
|
||||
const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
|
||||
result.push({
|
||||
startLineNumber: range.startLineNumber,
|
||||
endLineNumber: range.endLineNumber,
|
||||
isCollapsed: range.isCollapsed,
|
||||
isUserDefined: range.isRecovered,
|
||||
checksum: checksum
|
||||
});
|
||||
}
|
||||
|
@ -188,7 +206,7 @@ export class FoldingModel {
|
|||
if (!Array.isArray(state)) {
|
||||
return;
|
||||
}
|
||||
const hiddenRanges: FoldRange[] = [];
|
||||
const rangesToRestore: FoldRange[] = [];
|
||||
const maxLineNumber = this._textModel.getLineCount();
|
||||
for (const range of state) {
|
||||
if (range.startLineNumber >= range.endLineNumber || range.startLineNumber < 1 || range.endLineNumber > maxLineNumber) {
|
||||
|
@ -196,17 +214,19 @@ export class FoldingModel {
|
|||
}
|
||||
const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
|
||||
if (!range.checksum || checksum === range.checksum) {
|
||||
hiddenRanges.push({
|
||||
const isUserDefined = range.isUserDefined === true;
|
||||
rangesToRestore.push({
|
||||
startLineNumber: range.startLineNumber,
|
||||
endLineNumber: range.endLineNumber,
|
||||
type: undefined,
|
||||
isCollapsed: true,
|
||||
isManualSelection: true // converts to false when provider sends a match
|
||||
isCollapsed: range.isCollapsed ?? true,
|
||||
isUserDefined,
|
||||
isRecovered: !isUserDefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, hiddenRanges, maxLineNumber);
|
||||
const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, rangesToRestore, maxLineNumber);
|
||||
this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ export interface FoldRange {
|
|||
endLineNumber: number;
|
||||
type: string | undefined;
|
||||
isCollapsed: boolean;
|
||||
isManualSelection: boolean;
|
||||
isUserDefined: boolean;
|
||||
isRecovered: boolean;
|
||||
}
|
||||
|
||||
export const MAX_FOLDING_REGIONS = 0xFFFF;
|
||||
|
@ -21,11 +22,38 @@ export const MAX_LINE_NUMBER = 0xFFFFFF;
|
|||
|
||||
const MASK_INDENT = 0xFF000000;
|
||||
|
||||
class BitField {
|
||||
private readonly _states: Uint32Array;
|
||||
constructor(size: number) {
|
||||
const numWords = Math.ceil(size / 32);
|
||||
this._states = new Uint32Array(numWords);
|
||||
}
|
||||
|
||||
public get(index: number): boolean {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
return (this._states[arrayIndex] & (1 << bit)) !== 0;
|
||||
}
|
||||
|
||||
public set(index: number, newState: boolean) {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
const value = this._states[arrayIndex];
|
||||
if (newState) {
|
||||
this._states[arrayIndex] = value | (1 << bit);
|
||||
} else {
|
||||
this._states[arrayIndex] = value & ~(1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FoldingRegions {
|
||||
private readonly _startIndexes: Uint32Array;
|
||||
private readonly _endIndexes: Uint32Array;
|
||||
private readonly _collapseStates: Uint32Array;
|
||||
private readonly _manualStates: Uint32Array;
|
||||
private readonly _collapseStates: BitField;
|
||||
private readonly _userDefinedStates: BitField;
|
||||
private readonly _recoveredStates: BitField;
|
||||
|
||||
private _parentsComputed: boolean;
|
||||
private readonly _types: Array<string | undefined> | undefined;
|
||||
|
||||
|
@ -35,9 +63,9 @@ export class FoldingRegions {
|
|||
}
|
||||
this._startIndexes = startIndexes;
|
||||
this._endIndexes = endIndexes;
|
||||
const numWords = Math.ceil(startIndexes.length / 32);
|
||||
this._collapseStates = new Uint32Array(numWords);
|
||||
this._manualStates = new Uint32Array(numWords);
|
||||
this._collapseStates = new BitField(startIndexes.length);
|
||||
this._userDefinedStates = new BitField(startIndexes.length);
|
||||
this._recoveredStates = new BitField(startIndexes.length);
|
||||
this._types = types;
|
||||
this._parentsComputed = false;
|
||||
}
|
||||
|
@ -88,37 +116,27 @@ export class FoldingRegions {
|
|||
}
|
||||
|
||||
public isCollapsed(index: number): boolean {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
return (this._collapseStates[arrayIndex] & (1 << bit)) !== 0;
|
||||
return this._collapseStates.get(index);
|
||||
}
|
||||
|
||||
public setCollapsed(index: number, newState: boolean) {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
const value = this._collapseStates[arrayIndex];
|
||||
if (newState) {
|
||||
this._collapseStates[arrayIndex] = value | (1 << bit);
|
||||
} else {
|
||||
this._collapseStates[arrayIndex] = value & ~(1 << bit);
|
||||
}
|
||||
this._collapseStates.set(index, newState);
|
||||
}
|
||||
|
||||
public isManualSelection(index: number): boolean {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
return (this._manualStates[arrayIndex] & (1 << bit)) !== 0;
|
||||
public isUserDefined(index: number): boolean {
|
||||
return this._userDefinedStates.get(index);
|
||||
}
|
||||
|
||||
public setManualSelection(index: number, newState: boolean) {
|
||||
const arrayIndex = (index / 32) | 0;
|
||||
const bit = index % 32;
|
||||
const value = this._manualStates[arrayIndex];
|
||||
if (newState) {
|
||||
this._manualStates[arrayIndex] = value | (1 << bit);
|
||||
} else {
|
||||
this._manualStates[arrayIndex] = value & ~(1 << bit);
|
||||
}
|
||||
public setUserDefined(index: number, newState: boolean) {
|
||||
return this._userDefinedStates.set(index, newState);
|
||||
}
|
||||
|
||||
public isRecovered(index: number): boolean {
|
||||
return this._recoveredStates.get(index);
|
||||
}
|
||||
|
||||
public setRecovered(index: number, newState: boolean) {
|
||||
return this._recoveredStates.set(index, newState);
|
||||
}
|
||||
|
||||
public setCollapsedAllOfType(type: string, newState: boolean) {
|
||||
|
@ -188,7 +206,7 @@ export class FoldingRegions {
|
|||
public toString() {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
res[i] = `[${this.isManualSelection(i) ? '*' : ' '}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
|
||||
res[i] = `[${this.isUserDefined(i) ? '*' : ' '}${this.isRecovered(i) ? 'r' : ' '}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
|
||||
}
|
||||
return res.join(', ');
|
||||
}
|
||||
|
@ -199,7 +217,8 @@ export class FoldingRegions {
|
|||
endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,
|
||||
type: this._types ? this._types[index] : undefined,
|
||||
isCollapsed: this.isCollapsed(index),
|
||||
isManualSelection: this.isManualSelection(index)
|
||||
isUserDefined: this.isUserDefined(index),
|
||||
isRecovered: this.isRecovered(index)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -226,8 +245,11 @@ export class FoldingRegions {
|
|||
if (ranges[i].isCollapsed) {
|
||||
regions.setCollapsed(i, true);
|
||||
}
|
||||
if (ranges[i].isManualSelection) {
|
||||
regions.setManualSelection(i, true);
|
||||
if (ranges[i].isUserDefined) {
|
||||
regions.setUserDefined(i, true);
|
||||
}
|
||||
if (ranges[i].isRecovered) {
|
||||
regions.setRecovered(i, true);
|
||||
}
|
||||
}
|
||||
return regions;
|
||||
|
@ -237,11 +259,10 @@ export class FoldingRegions {
|
|||
* Two inputs, each a FoldingRegions or a FoldRange[], are merged.
|
||||
* Each input must be pre-sorted on startLineNumber.
|
||||
* The first list is assumed to always include all regions currently defined by range providers.
|
||||
* The second list only contains hidden ranges.
|
||||
* The second list only contains the previously collapsed and all manual ranges.
|
||||
* If the line position matches, the range of the new range is taken, and the range is no longer manual
|
||||
* When an entry in one list overlaps an entry in the other, the second list's entry "wins" and
|
||||
* overlapping entries in the first list are discarded. With one exception: when there is just
|
||||
* one such second list entry and it is not manual it is discarded, on the assumption that
|
||||
* user editing has resulted in the range no longer existing.
|
||||
* overlapping entries in the first list are discarded.
|
||||
* Invalid entries are discarded. An entry is invalid if:
|
||||
* the start and end line numbers aren't a valid range of line numbers,
|
||||
* it is out of sequence or has the same start line as a preceding entry,
|
||||
|
@ -252,18 +273,6 @@ export class FoldingRegions {
|
|||
rangesB: FoldingRegions | FoldRange[],
|
||||
maxLineNumber: number | undefined): FoldRange[] {
|
||||
maxLineNumber = maxLineNumber ?? Number.MAX_VALUE;
|
||||
let result = this._trySanitizeAndMerge(1, rangesA, rangesB, maxLineNumber);
|
||||
if (!result) { // try again, converting hidden ranges to manually selected
|
||||
result = this._trySanitizeAndMerge(2, rangesA, rangesB, maxLineNumber);
|
||||
}
|
||||
return result!;
|
||||
}
|
||||
|
||||
private static _trySanitizeAndMerge(
|
||||
passNumber: number, // it can take two passes to get this done
|
||||
rangesA: FoldingRegions | FoldRange[],
|
||||
rangesB: FoldingRegions | FoldRange[],
|
||||
maxLineNumber: number): FoldRange[] | null {
|
||||
|
||||
const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => {
|
||||
return Array.isArray(r)
|
||||
|
@ -281,41 +290,34 @@ export class FoldingRegions {
|
|||
let topStackedRange: FoldRange | undefined;
|
||||
let prevLineNumber = 0;
|
||||
const resultRanges: FoldRange[] = [];
|
||||
let numberAutoExpand = 0;
|
||||
|
||||
while (nextA || nextB) {
|
||||
|
||||
let useRange: FoldRange | undefined = undefined;
|
||||
if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) {
|
||||
// nextB is next
|
||||
if (nextA
|
||||
&& nextA.startLineNumber === nextB.startLineNumber
|
||||
&& nextA.endLineNumber === nextB.endLineNumber) {
|
||||
// same range in both lists, merge the details
|
||||
useRange = nextB;
|
||||
useRange.isCollapsed = useRange.isCollapsed || nextA.isCollapsed;
|
||||
// next line removes manual flag when range provider has matching range
|
||||
useRange.isManualSelection = nextA.isManualSelection && nextB.isManualSelection;
|
||||
if (!useRange.type) {
|
||||
useRange.type = nextA.type;
|
||||
if (nextA && nextA.startLineNumber === nextB.startLineNumber) {
|
||||
if (nextB.isUserDefined) {
|
||||
// a user defined range (possibly unfolded)
|
||||
useRange = nextB;
|
||||
} else {
|
||||
// a previously folded range or a (possibly unfolded) recovered range
|
||||
useRange = nextA;
|
||||
useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber;
|
||||
useRange.isUserDefined = false;
|
||||
useRange.isRecovered = false;
|
||||
}
|
||||
nextA = getA(++indexA); // not necessary, just for speed
|
||||
} else if (nextB.isCollapsed && !nextB.isManualSelection && passNumber === 1) {
|
||||
if (++numberAutoExpand > 1) {
|
||||
// do second pass keeping these, assuming something like an unmatched /*
|
||||
return null;
|
||||
}
|
||||
// skip nextB (auto expand) by not setting useRange, assuming it was edited
|
||||
} else { // use nextB
|
||||
} else {
|
||||
useRange = nextB;
|
||||
if (useRange.isCollapsed) {
|
||||
// doesn't match nextA, convert to a manual selection if it wasn't already
|
||||
useRange.isManualSelection = true;
|
||||
if (nextB.isCollapsed && !nextB.isUserDefined && !nextB.isRecovered) {
|
||||
// a previously collapsed range
|
||||
useRange.isRecovered = true;
|
||||
useRange.isUserDefined = false;
|
||||
}
|
||||
}
|
||||
nextB = getB(++indexB);
|
||||
} else {
|
||||
// nextA is next. The B set takes precedence and we sometimes need to look
|
||||
// nextA is next. The user folded B set takes precedence and we sometimes need to look
|
||||
// ahead in it to check for an upcoming conflict.
|
||||
let scanIndex = indexB;
|
||||
let prescanB = nextB;
|
||||
|
@ -324,8 +326,8 @@ export class FoldingRegions {
|
|||
useRange = nextA;
|
||||
break; // no conflict, use this nextA
|
||||
}
|
||||
if (prescanB.endLineNumber > nextA!.endLineNumber
|
||||
&& (!prescanB.isCollapsed || prescanB.isManualSelection || passNumber === 2)) {
|
||||
if (prescanB.isUserDefined && prescanB.endLineNumber > nextA!.endLineNumber) {
|
||||
// we found a user folded range, it wins
|
||||
break; // without setting nextResult, so this nextA gets skipped
|
||||
}
|
||||
prescanB = getB(++scanIndex);
|
||||
|
|
|
@ -14,22 +14,30 @@ const markers: FoldingMarkers = {
|
|||
end: /^\s*#endregion\b/
|
||||
};
|
||||
|
||||
enum State {
|
||||
none = 0,
|
||||
userDefined = 1,
|
||||
recovered = 2
|
||||
}
|
||||
|
||||
suite('FoldingRanges', () => {
|
||||
|
||||
const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, manual: boolean | undefined = undefined, type: string | undefined = undefined) =>
|
||||
const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, state: State = State.none, type: string | undefined = undefined) =>
|
||||
<FoldRange>{
|
||||
startLineNumber: from,
|
||||
endLineNumber: to,
|
||||
type: type,
|
||||
isCollapsed: collapsed || false,
|
||||
isManualSelection: manual || false
|
||||
isUserDefined: state === State.userDefined,
|
||||
isRecovered: state === State.recovered,
|
||||
};
|
||||
const assertEqualRanges = (range1: FoldRange, range2: FoldRange, msg: string) => {
|
||||
assert.strictEqual(range1.startLineNumber, range2.startLineNumber, msg + ' start');
|
||||
assert.strictEqual(range1.endLineNumber, range2.endLineNumber, msg + ' end');
|
||||
assert.strictEqual(range1.type, range2.type, msg + ' type');
|
||||
assert.strictEqual(range1.isCollapsed, range2.isCollapsed, msg + ' collapsed');
|
||||
assert.strictEqual(range1.isManualSelection, range2.isManualSelection, msg + ' manual');
|
||||
assert.strictEqual(range1.isUserDefined, range2.isUserDefined, msg + ' userDefined');
|
||||
assert.strictEqual(range1.isRecovered, range2.isRecovered, msg + ' recovered');
|
||||
};
|
||||
|
||||
test('test max folding regions', () => {
|
||||
|
@ -122,110 +130,88 @@ suite('FoldingRanges', () => {
|
|||
test('sanitizeAndMerge1', () => {
|
||||
const regionSet1: FoldRange[] = [
|
||||
foldRange(0, 100), // invalid, should be removed
|
||||
foldRange(1, 100, false, false, 'A'), // valid
|
||||
foldRange(1, 100, false, false, 'Z'), // invalid, duplicate start
|
||||
foldRange(1, 100, false, State.none, 'A'), // valid
|
||||
foldRange(1, 100, false, State.none, 'Z'), // invalid, duplicate start
|
||||
foldRange(10, 10, false), // invalid, should be removed
|
||||
foldRange(20, 80, false, false, 'C1'), // valid inside 'B'
|
||||
foldRange(22, 80, true, false, 'D1'), // valid inside 'C1'
|
||||
foldRange(20, 80, false, State.none, 'C1'), // valid inside 'B'
|
||||
foldRange(22, 80, true, State.none, 'D1'), // valid inside 'C1'
|
||||
foldRange(90, 101), // invalid, should be removed
|
||||
];
|
||||
const regionSet2: FoldRange[] = [
|
||||
foldRange(2, 100, false, false, 'B'), // valid, inside 'A'
|
||||
foldRange(20, 80, true), // should merge with C1
|
||||
foldRange(18, 80, true), // invalid, out of order
|
||||
foldRange(21, 81, true, false, 'Z'), // invalid, overlapping
|
||||
foldRange(22, 80, false, false, 'D2'), // should merge with D1
|
||||
foldRange(21, 81, true, State.none, 'Z'), // invalid, overlapping
|
||||
foldRange(22, 80, true, State.none, 'D2'), // should merge with D1
|
||||
];
|
||||
let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
assert.strictEqual(result.length, 4, 'result length1');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A1');
|
||||
assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B1');
|
||||
assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C1');
|
||||
assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D1');
|
||||
const regionClass1 = FoldingRegions.fromFoldRanges(regionSet1);
|
||||
const regionClass2 = FoldingRegions.fromFoldRanges(regionSet2);
|
||||
// same tests again with inputs as FoldingRegions instead of FoldRange[]
|
||||
result = FoldingRegions.sanitizeAndMerge(regionClass1, regionClass2, 100);
|
||||
assert.strictEqual(result.length, 4, 'result length2');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A2');
|
||||
assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B2');
|
||||
assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C2');
|
||||
assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D2');
|
||||
const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
assert.strictEqual(result.length, 3, 'result length1');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, State.none, 'A'), 'A1');
|
||||
assertEqualRanges(result[1], foldRange(20, 80, true, State.none, 'C1'), 'C1');
|
||||
assertEqualRanges(result[2], foldRange(22, 80, true, State.none, 'D1'), 'D1');
|
||||
});
|
||||
|
||||
test('sanitizeAndMerge2', () => {
|
||||
const regionSet1: FoldRange[] = [
|
||||
foldRange(1, 100, false, false, 'a1'), // valid
|
||||
foldRange(2, 100, false, false, 'a2'), // valid
|
||||
foldRange(3, 19, false, false, 'a3'), // valid
|
||||
foldRange(20, 71, false, false, 'a4'), // overlaps b3
|
||||
foldRange(21, 29, false, false, 'a5'), // valid
|
||||
foldRange(81, 91, false, false, 'a6'), // overlaps b4
|
||||
foldRange(1, 100, false, State.none, 'a1'), // valid
|
||||
foldRange(2, 100, false, State.none, 'a2'), // valid
|
||||
foldRange(3, 19, false, State.none, 'a3'), // valid
|
||||
foldRange(20, 71, false, State.none, 'a4'), // overlaps b3
|
||||
foldRange(21, 29, false, State.none, 'a5'), // valid
|
||||
foldRange(81, 91, false, State.none, 'a6'), // overlaps b4
|
||||
];
|
||||
const regionSet2: FoldRange[] = [
|
||||
foldRange(30, 39, false, false, 'b1'), // valid
|
||||
foldRange(40, 49, false, false, 'b2'), // valid
|
||||
foldRange(50, 100, false, false, 'b3'), // overlaps a4
|
||||
foldRange(80, 90, false, false, 'b4'), // overlaps a6
|
||||
foldRange(92, 100, false, false, 'b5'), // valid
|
||||
foldRange(30, 39, true, State.none, 'b1'), // valid, will be recovered
|
||||
foldRange(40, 49, true, State.userDefined, 'b2'), // valid
|
||||
foldRange(50, 100, true, State.userDefined, 'b3'), // overlaps a4
|
||||
foldRange(80, 90, true, State.userDefined, 'b4'), // overlaps a6
|
||||
foldRange(92, 100, true, State.userDefined, 'b5'), // valid
|
||||
];
|
||||
let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
assert.strictEqual(result.length, 9, 'result length1');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'P1');
|
||||
assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'P2');
|
||||
assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'P3');
|
||||
assertEqualRanges(result[3], foldRange(21, 29, false, false, 'a5'), 'P4');
|
||||
assertEqualRanges(result[4], foldRange(30, 39, false, false, 'b1'), 'P5');
|
||||
assertEqualRanges(result[5], foldRange(40, 49, false, false, 'b2'), 'P6');
|
||||
assertEqualRanges(result[6], foldRange(50, 100, false, false, 'b3'), 'P7');
|
||||
assertEqualRanges(result[7], foldRange(80, 90, false, false, 'b4'), 'P8');
|
||||
assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'P9');
|
||||
// reverse the two inputs
|
||||
result = FoldingRegions.sanitizeAndMerge(regionSet2, regionSet1, 100);
|
||||
assert.strictEqual(result.length, 9, 'result length2');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'Q1');
|
||||
assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'Q2');
|
||||
assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'Q3');
|
||||
assertEqualRanges(result[3], foldRange(20, 71, false, false, 'a4'), 'Q4');
|
||||
assertEqualRanges(result[4], foldRange(21, 29, false, false, 'a5'), 'Q5');
|
||||
assertEqualRanges(result[5], foldRange(30, 39, false, false, 'b1'), 'Q6');
|
||||
assertEqualRanges(result[6], foldRange(40, 49, false, false, 'b2'), 'Q7');
|
||||
assertEqualRanges(result[7], foldRange(81, 91, false, false, 'a6'), 'Q8');
|
||||
assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'Q9');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, State.none, 'a1'), 'P1');
|
||||
assertEqualRanges(result[1], foldRange(2, 100, false, State.none, 'a2'), 'P2');
|
||||
assertEqualRanges(result[2], foldRange(3, 19, false, State.none, 'a3'), 'P3');
|
||||
assertEqualRanges(result[3], foldRange(21, 29, false, State.none, 'a5'), 'P4');
|
||||
assertEqualRanges(result[4], foldRange(30, 39, true, State.recovered, 'b1'), 'P5');
|
||||
assertEqualRanges(result[5], foldRange(40, 49, true, State.userDefined, 'b2'), 'P6');
|
||||
assertEqualRanges(result[6], foldRange(50, 100, true, State.userDefined, 'b3'), 'P7');
|
||||
assertEqualRanges(result[7], foldRange(80, 90, true, State.userDefined, 'b4'), 'P8');
|
||||
assertEqualRanges(result[8], foldRange(92, 100, true, State.userDefined, 'b5'), 'P9');
|
||||
});
|
||||
|
||||
test('sanitizeAndMerge3', () => {
|
||||
const regionSet1: FoldRange[] = [
|
||||
foldRange(1, 100, false, false, 'a1'), // valid
|
||||
foldRange(10, 29, false, false, 'a2'), // matches manual hidden
|
||||
foldRange(35, 39, true, true, 'a3'), // valid
|
||||
foldRange(1, 100, false, State.none, 'a1'), // valid
|
||||
foldRange(10, 29, false, State.none, 'a2'), // matches manual hidden
|
||||
foldRange(35, 39, true, State.recovered, 'a3'), // valid
|
||||
];
|
||||
const regionSet2: FoldRange[] = [
|
||||
foldRange(10, 29, true, true, 'b1'), // matches a
|
||||
foldRange(20, 28, true, false, 'b2'), // should get dropped
|
||||
foldRange(30, 39, true, true, 'b3'), // should remain
|
||||
foldRange(10, 29, true, State.recovered, 'b1'), // matches a
|
||||
foldRange(20, 28, true, State.none, 'b2'), // should remain
|
||||
foldRange(30, 39, true, State.recovered, 'b3'), // should remain
|
||||
];
|
||||
const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
assert.strictEqual(result.length, 4, 'result length3');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1');
|
||||
assertEqualRanges(result[1], foldRange(10, 29, true, false, 'b1'), 'R2');
|
||||
assertEqualRanges(result[2], foldRange(30, 39, true, true, 'b3'), 'R3');
|
||||
assertEqualRanges(result[3], foldRange(35, 39, true, true, 'a3'), 'R4');
|
||||
assert.strictEqual(result.length, 5, 'result length3');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, State.none, 'a1'), 'R1');
|
||||
assertEqualRanges(result[1], foldRange(10, 29, true, State.none, 'a2'), 'R2');
|
||||
assertEqualRanges(result[2], foldRange(20, 28, true, State.recovered, 'b2'), 'R3');
|
||||
assertEqualRanges(result[3], foldRange(30, 39, true, State.recovered, 'b3'), 'R3');
|
||||
assertEqualRanges(result[4], foldRange(35, 39, true, State.recovered, 'a3'), 'R4');
|
||||
});
|
||||
|
||||
test('sanitizeAndMerge4', () => {
|
||||
const regionSet1: FoldRange[] = [
|
||||
foldRange(1, 100, false, false, 'a1'), // valid
|
||||
foldRange(1, 100, false, State.none, 'a1'), // valid
|
||||
];
|
||||
const regionSet2: FoldRange[] = [
|
||||
foldRange(20, 28, true, false, 'b1'), // hidden
|
||||
foldRange(30, 38, true, false, 'b2'), // hidden
|
||||
foldRange(20, 28, true, State.none, 'b1'), // hidden
|
||||
foldRange(30, 38, true, State.none, 'b2'), // hidden
|
||||
];
|
||||
const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
|
||||
assert.strictEqual(result.length, 3, 'result length4');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1');
|
||||
assertEqualRanges(result[1], foldRange(20, 28, true, true, 'b1'), 'R2');
|
||||
assertEqualRanges(result[2], foldRange(30, 38, true, true, 'b2'), 'R3');
|
||||
assertEqualRanges(result[0], foldRange(1, 100, false, State.none, 'a1'), 'R1');
|
||||
assertEqualRanges(result[1], foldRange(20, 28, true, State.recovered, 'b1'), 'R2');
|
||||
assertEqualRanges(result[2], foldRange(30, 38, true, State.recovered, 'b2'), 'R3');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue