Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Collapsed Text #170447

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0086d4b
support showing collapsed text at the end of the first line of the fo…
mtbaqer Nov 23, 2022
e5aa75d
Merge branch 'microsoft:main' into collapsed-text
mtbaqer Nov 23, 2022
9357128
support startColumn in FoldingRange
mtbaqer Dec 19, 2022
44a9949
Merge branch 'main' into collapsed-text
mtbaqer Dec 19, 2022
461a54d
clean up
mtbaqer Dec 19, 2022
7b15ee6
pass missing foldingOffset arguments in couple places
mtbaqer Dec 19, 2022
098c709
unfold hidden ranges if selection is within the column range
mtbaqer Dec 20, 2022
783c1ed
support clicking collapsedText to unfold ranges with a set startColumn
mtbaqer Dec 20, 2022
373aaa2
hide decorations after the folding offset.
mtbaqer Dec 21, 2022
3a3fe9d
fix startColumn comparison in FoldingRegions.sanitizeAndMerge
mtbaqer Dec 21, 2022
78c3e7f
update foldingModel tests to include startColumn cases
mtbaqer Dec 21, 2022
d591451
update sanitizeAndMerge validation to include cases where ranges star…
mtbaqer Dec 21, 2022
c2dbf98
fix sanitizeAndMerge not throwing out ranges starting at line 0 as a …
mtbaqer Dec 21, 2022
a5706ed
update foldingRanges tests to include startColumn cases
mtbaqer Dec 21, 2022
001846b
test foldingDecorations
mtbaqer Dec 22, 2022
e353b18
support having an empty collapsedText
mtbaqer Dec 23, 2022
8bec5d8
update modelLineProjection tests to include inline fold cases
mtbaqer Dec 23, 2022
e98387c
move collapsedText and startColumn properties to vscode.proposed.coll…
mtbaqer Jan 2, 2023
b9b4ece
Merge branch 'main' into collapsed-text
mtbaqer Jan 2, 2023
b642001
make sanitizeAndMerge consider ranges duplicate if one has startColum…
mtbaqer Jan 2, 2023
84ba9e5
clean up
mtbaqer Jan 2, 2023
6e15aa2
Merge branch 'main' into collapsed-text
mtbaqer Jan 3, 2023
5ed4abc
enforce enabledApiProposals checking for collapsedText
mtbaqer Jan 5, 2023
4159152
Merge branch 'main' into collapsed-text
mtbaqer Jan 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
support startColumn in FoldingRange
mtbaqer committed Dec 19, 2022
commit 93571289fc7435cbf71b8c7c925e3d62c035ee73
20 changes: 13 additions & 7 deletions src/vs/editor/browser/view/domLineBreaksComputer.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import { StringBuilder } from 'vs/editor/common/core/stringBuilder';
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
import { LineInjectedText } from 'vs/editor/common/textModelEvents';
import { InlineFoldRange, LineInjectedText } from 'vs/editor/common/textModelEvents';
import { InjectedTextOptions } from 'vs/editor/common/model';
import { ILineBreaksComputer, ILineBreaksComputerFactory, ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData';

@@ -27,19 +27,21 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ILineBreaksComputer {
const requests: string[] = [];
const injectedTexts: (LineInjectedText[] | null)[] = [];
const inlineFoldsRanges: (InlineFoldRange[] | null)[] = [];
return {
addRequest: (lineText: string, injectedText: LineInjectedText[] | null, previousLineBreakData: ModelLineProjectionData | null) => {
addRequest: (lineText: string, injectedText: LineInjectedText[] | null, inlineFolds: InlineFoldRange[] | null, previousLineBreakData: ModelLineProjectionData | null) => {
requests.push(lineText);
injectedTexts.push(injectedText);
inlineFoldsRanges.push(inlineFolds);
},
finalize: () => {
return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts);
return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts, inlineFoldsRanges);
}
};
}
}

function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', injectedTextsPerLine: (LineInjectedText[] | null)[]): (ModelLineProjectionData | null)[] {
function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', injectedTextsPerLine: (LineInjectedText[] | null)[], inlineFoldsPerLine: (InlineFoldRange[] | null)[]): (ModelLineProjectionData | null)[] {
function createEmptyLineBreakWithPossiblyInjectedText(requestIdx: number): ModelLineProjectionData | null {
const injectedTexts = injectedTextsPerLine[requestIdx];
if (injectedTexts) {
@@ -50,7 +52,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe

// creating a `LineBreakData` with an invalid `breakOffsetsVisibleColumn` is OK
// because `breakOffsetsVisibleColumn` will never be used because it contains injected text
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], null, 0);
} else {
return null;
}
@@ -79,7 +81,8 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe
const allCharOffsets: number[][] = [];
const allVisibleColumns: number[][] = [];
for (let i = 0; i < requests.length; i++) {
const lineContent = LineInjectedText.applyInjectedText(requests[i], injectedTextsPerLine[i]);
const contentWithInjectedText = LineInjectedText.applyInjectedText(requests[i], injectedTextsPerLine[i]);
const lineContent = InlineFoldRange.applyInlineFoldsWithInjectedText(contentWithInjectedText, inlineFoldsPerLine[i], injectedTextsPerLine[i]);

let firstNonWhitespaceIndex = 0;
let wrappedTextIndentLength = 0;
@@ -179,7 +182,10 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe
injectionOffsets = null;
}

result[i] = new ModelLineProjectionData(injectionOffsets, injectionOptions, breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength);
const curInlineFolds = inlineFoldsPerLine[i];
const foldingOffset = InlineFoldRange.getFoldingOffset(curInlineFolds, curInjectedTexts);

result[i] = new ModelLineProjectionData(injectionOffsets, injectionOptions, breakOffsets, breakOffsetsVisibleColumn, foldingOffset, wrappedTextIndentLength);
}

document.body.removeChild(containerDomNode);
7 changes: 6 additions & 1 deletion src/vs/editor/common/languages.ts
Original file line number Diff line number Diff line change
@@ -1355,10 +1355,15 @@ export interface FoldingRangeProvider {
export interface FoldingRange {

/**
* The one-based start line of the range to fold. The folded area starts after the line's last character.
* The one-based start line of the range to fold.
*/
start: number;

/**
* The one-based start column of the range to fold. If not defined, folded area starts at the end of start line.
*/
startColumn?: number;

/**
* The one-based end line of the range to fold. The folded area ends with the line's last character.
*/
15 changes: 15 additions & 0 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
@@ -188,6 +188,15 @@ export interface IModelDecorationOptions {
* @internal
*/
hideInStringTokens?: boolean | null;

/**
* If set, this decoration inline content will not be rendered.
* Only applies to start line if range is multiline.
* This decorations's injected text will still render normally.
* !!Currently only supports hiding to the end of the line.!!
* @internal
*/
hideContent?: boolean | null;
}

/**
@@ -974,6 +983,12 @@ export interface ITextModel {
*/
getInjectedTextDecorations(ownerId?: number): IModelDecoration[];

/**
* Gets all the decorations that contain an inline fold.
* @param ownerId If set, it will ignore decorations belonging to other owners.
*/
getInlineFoldsDecorations(ownerId?: number): IModelDecoration[];

/**
* @internal
*/
64 changes: 57 additions & 7 deletions src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeText
import { SearchParams, TextModelSearch } from 'vs/editor/common/model/textModelSearch';
import { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';
import { IBracketPairsTextModelPart } from 'vs/editor/common/textModelBracketPairs';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptionsChangedEvent, InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawChange, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptionsChangedEvent, InlineFoldRange, InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawChange, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents';
import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides';
import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart';
import { IColorTheme, ThemeColor } from 'vs/platform/theme/common/themeService';
@@ -1443,29 +1443,48 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
const firstEditLineNumber = currentEditStartLineNumber;
const lastInsertedLineNumber = currentEditStartLineNumber + insertingLinesCnt;

const currentEditStartOffset = this.getOffsetAt(new Position(firstEditLineNumber, 1));
const currentEditEndOffset = this.getOffsetAt(new Position(lastInsertedLineNumber, this.getLineMaxColumn(lastInsertedLineNumber)));

const decorationsWithInjectedTextInEditedRange = this._decorationsTree.getInjectedTextInInterval(
this,
this.getOffsetAt(new Position(firstEditLineNumber, 1)),
this.getOffsetAt(new Position(lastInsertedLineNumber, this.getLineMaxColumn(lastInsertedLineNumber))),
currentEditStartOffset,
currentEditEndOffset,
0
);

const decorationsWithInlineFoldsInEditedRange = this._decorationsTree.getInlineFoldsInInterval(
this,
currentEditStartOffset,
currentEditEndOffset,
0
);


const injectedTextInEditedRange = LineInjectedText.fromDecorations(decorationsWithInjectedTextInEditedRange);
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);

const inlineFoldsInEditedRange = InlineFoldRange.fromDecorations(decorationsWithInlineFoldsInEditedRange);
const inlineFoldsInEditedRangeQueue = new ArrayQueue(inlineFoldsInEditedRange);


for (let j = editingLinesCnt; j >= 0; j--) {
const editLineNumber = startLineNumber + j;
const currentEditLineNumber = currentEditStartLineNumber + j;

injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber);
const decorationsInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);
const injectedTextInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);


inlineFoldsInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber);
const inlineFoldsInCurrentLine = inlineFoldsInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);

rawContentChanges.push(
new ModelRawLineChanged(
editLineNumber,
this.getLineContent(currentEditLineNumber),
decorationsInCurrentLine
injectedTextInCurrentLine,
inlineFoldsInCurrentLine
));
}

@@ -1477,26 +1496,32 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati

if (editingLinesCnt < insertingLinesCnt) {
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);
const inlineFoldsInEditedRangeQueue = new ArrayQueue(inlineFoldsInEditedRange);
// Must insert some lines
const spliceLineNumber = startLineNumber + editingLinesCnt;
const cnt = insertingLinesCnt - editingLinesCnt;
const fromLineNumber = newLineCount - lineCount - cnt + spliceLineNumber + 1;
const injectedTexts: (LineInjectedText[] | null)[] = [];
const inlineFolds: (InlineFoldRange[] | null)[] = [];
const newLines: string[] = [];
for (let i = 0; i < cnt; i++) {
const lineNumber = fromLineNumber + i;
newLines[i] = this.getLineContent(lineNumber);

injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber);
injectedTexts[i] = injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber);

inlineFoldsInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber);
inlineFolds[i] = inlineFoldsInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber);
}

rawContentChanges.push(
new ModelRawLinesInserted(
spliceLineNumber + 1,
startLineNumber + insertingLinesCnt,
newLines,
injectedTexts
injectedTexts,
inlineFolds
)
);
}
@@ -1553,7 +1578,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
}

const affectedLines = Array.from(affectedInjectedTextLines);
const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber)));
const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber), this._getInlineFoldsInLine(lineNumber)));

this._onDidChangeInjectedText.fire(new ModelInjectedTextChangedEvent(lineChangeEvents));
}
@@ -1738,6 +1763,18 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
return LineInjectedText.fromDecorations(result).filter(t => t.lineNumber === lineNumber);
}

public getInlineFoldsDecorations(ownerId: number = 0): model.IModelDecoration[] {
return this.getInjectedTextDecorations(ownerId).filter(d => d.options.hideContent);
}

private _getInlineFoldsInLine(lineNumber: number): InlineFoldRange[] {
const startOffset = this._buffer.getOffsetAt(lineNumber, 1);
const endOffset = startOffset + this._buffer.getLineLength(lineNumber);

const result = this._decorationsTree.getInjectedTextInInterval(this, startOffset, endOffset, 0).filter(d => d.options.hideContent);
return InlineFoldRange.fromDecorations(result).filter(t => t.lineNumber === lineNumber);
}

public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): model.IModelDecoration[] {
let result = this._decorationsTree.getAll(this, ownerId, filterOutValidation, false);
result = result.concat(this._decorationProvider.getAllDecorations(ownerId, filterOutValidation));
@@ -2034,6 +2071,15 @@ class DecorationsTrees {
return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty());
}

public getInlineFoldsInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] {
return this.getInjectedTextInInterval(host, start, end, filterOwnerId).filter((i) => i.options.hideContent);
}

public getAllInlineFolds(host: IDecorationsTreesHost, filterOwnerId: number): model.IModelDecoration[] {
return this.getAllInjectedText(host, filterOwnerId).filter((i) => i.options.hideContent);
}


public getAll(host: IDecorationsTreesHost, filterOwnerId: number, filterOutValidation: boolean, overviewRulerOnly: boolean): model.IModelDecoration[] {
const versionId = host.getVersionId();
const result = this._search(filterOwnerId, filterOutValidation, overviewRulerOnly, versionId);
@@ -2259,6 +2305,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
readonly before: ModelDecorationInjectedTextOptions | null;
readonly hideInCommentTokens: boolean | null;
readonly hideInStringTokens: boolean | null;
readonly hideContent?: boolean | null | undefined;


private constructor(options: model.IModelDecorationOptions) {
@@ -2287,6 +2334,9 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null;
this.hideInCommentTokens = options.hideInCommentTokens ?? false;
this.hideInStringTokens = options.hideInStringTokens ?? false;
this.hideContent = options.hideContent ?? false;
console.log({ hideContent: options.hideContent });

}
}
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' });
20 changes: 17 additions & 3 deletions src/vs/editor/common/modelLineProjectionData.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { Position } from 'vs/editor/common/core/position';
import { InjectedTextCursorStops, InjectedTextOptions, PositionAffinity } from 'vs/editor/common/model';
import { LineInjectedText } from 'vs/editor/common/textModelEvents';
import { InlineFoldRange, LineInjectedText } from 'vs/editor/common/textModelEvents';

/**
* *input*:
@@ -28,15 +28,25 @@ import { LineInjectedText } from 'vs/editor/common/textModelEvents';
* xxxxxx[ii]xxxx
* ```
*
* -> folding at offset `$` in `xxxxxx[iiiiiii|iii]xxxxxxxxxxx|xx$xxxx[ii]xxxx|`:
* ```
* xxxxxx[iiiiiii
* iii]xxxxxxxxxxx
* xx
* ```
*
* -> applying wrappedTextIndentLength, *output*:
* ```
* xxxxxx[iiiiiii
* iii]xxxxxxxxxxx
* xxxxxx[ii]xxxx
* xx
* ```
*/
export class ModelLineProjectionData {
constructor(
/**
* Offsets after foldingOffset are still intact.
*/
public injectionOffsets: number[] | null,
/**
* `injectionOptions.length` must equal `injectionOffsets.length`
@@ -51,6 +61,10 @@ export class ModelLineProjectionData {
* Refers to offsets after applying injections
*/
public breakOffsetsVisibleColumn: number[],
/**
* Refers to folding offset after applying injections to the source.
*/
public foldingOffset: number | null,
public wrappedTextIndentLength: number
) {
}
@@ -336,6 +350,6 @@ export interface ILineBreaksComputer {
/**
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
*/
addRequest(lineText: string, injectedText: LineInjectedText[] | null, previousLineBreakData: ModelLineProjectionData | null): void;
addRequest(lineText: string, injectedText: LineInjectedText[] | null, inlineFolds: InlineFoldRange[] | null, previousLineBreakData: ModelLineProjectionData | null): void;
finalize(): (ModelLineProjectionData | null)[];
}
Loading