Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/model/ghostText.ts
4797 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { equals } from '../../../../../base/common/arrays.js';
7
import { splitLines } from '../../../../../base/common/strings.js';
8
import { Position } from '../../../../common/core/position.js';
9
import { Range } from '../../../../common/core/range.js';
10
import { TextReplacement, TextEdit } from '../../../../common/core/edits/textEdit.js';
11
import { LineDecoration } from '../../../../common/viewLayout/lineDecorations.js';
12
import { ColumnRange } from '../../../../common/core/ranges/columnRange.js';
13
import { assertFn, checkAdjacentItems } from '../../../../../base/common/assert.js';
14
import { InlineDecoration } from '../../../../common/viewModel/inlineDecorations.js';
15
16
export class GhostText {
17
constructor(
18
public readonly lineNumber: number,
19
public readonly parts: GhostTextPart[],
20
) {
21
assertFn(() => checkAdjacentItems(parts, (p1, p2) => p1.column <= p2.column));
22
}
23
24
equals(other: GhostText): boolean {
25
return this.lineNumber === other.lineNumber &&
26
this.parts.length === other.parts.length &&
27
this.parts.every((part, index) => part.equals(other.parts[index]));
28
}
29
30
/**
31
* Only used for testing/debugging.
32
*/
33
render(documentText: string, debug: boolean = false): string {
34
return new TextEdit([
35
...this.parts.map(p => new TextReplacement(
36
Range.fromPositions(new Position(this.lineNumber, p.column)),
37
debug ? `[${p.lines.map(line => line.line).join('\n')}]` : p.lines.map(line => line.line).join('\n')
38
)),
39
]).applyToString(documentText);
40
}
41
42
renderForScreenReader(lineText: string): string {
43
if (this.parts.length === 0) {
44
return '';
45
}
46
const lastPart = this.parts[this.parts.length - 1];
47
48
const cappedLineText = lineText.substr(0, lastPart.column - 1);
49
const text = new TextEdit([
50
...this.parts.map(p => new TextReplacement(
51
Range.fromPositions(new Position(1, p.column)),
52
p.lines.map(line => line.line).join('\n')
53
)),
54
]).applyToString(cappedLineText);
55
56
return text.substring(this.parts[0].column - 1);
57
}
58
59
isEmpty(): boolean {
60
return this.parts.every(p => p.lines.length === 0);
61
}
62
63
get lineCount(): number {
64
return 1 + this.parts.reduce((r, p) => r + p.lines.length - 1, 0);
65
}
66
}
67
68
export interface IGhostTextLine {
69
line: string;
70
lineDecorations: LineDecoration[];
71
}
72
73
74
export class GhostTextPart {
75
76
readonly lines: IGhostTextLine[];
77
78
constructor(
79
readonly column: number,
80
readonly text: string,
81
/**
82
* Indicates if this part is a preview of an inline suggestion when a suggestion is previewed.
83
*/
84
readonly preview: boolean,
85
private _inlineDecorations: InlineDecoration[] = [],
86
) {
87
this.lines = splitLines(this.text).map((line, i) => ({
88
line,
89
lineDecorations: LineDecoration.filter(this._inlineDecorations, i + 1, 1, line.length + 1)
90
}));
91
}
92
93
equals(other: GhostTextPart): boolean {
94
return this.column === other.column &&
95
this.lines.length === other.lines.length &&
96
this.lines.every((line, index) =>
97
line.line === other.lines[index].line &&
98
LineDecoration.equalsArr(line.lineDecorations, other.lines[index].lineDecorations)
99
);
100
}
101
}
102
103
export class GhostTextReplacement {
104
public readonly parts: ReadonlyArray<GhostTextPart>;
105
readonly newLines: string[];
106
107
constructor(
108
readonly lineNumber: number,
109
readonly columnRange: ColumnRange,
110
readonly text: string,
111
public readonly additionalReservedLineCount: number = 0,
112
) {
113
this.parts = [
114
new GhostTextPart(
115
this.columnRange.endColumnExclusive,
116
this.text,
117
false
118
),
119
];
120
this.newLines = splitLines(this.text);
121
}
122
123
renderForScreenReader(_lineText: string): string {
124
return this.newLines.join('\n');
125
}
126
127
render(documentText: string, debug: boolean = false): string {
128
const replaceRange = this.columnRange.toRange(this.lineNumber);
129
130
if (debug) {
131
return new TextEdit([
132
new TextReplacement(Range.fromPositions(replaceRange.getStartPosition()), '('),
133
new TextReplacement(Range.fromPositions(replaceRange.getEndPosition()), `)[${this.newLines.join('\n')}]`),
134
]).applyToString(documentText);
135
} else {
136
return new TextEdit([
137
new TextReplacement(replaceRange, this.newLines.join('\n')),
138
]).applyToString(documentText);
139
}
140
}
141
142
get lineCount(): number {
143
return this.newLines.length;
144
}
145
146
isEmpty(): boolean {
147
return this.parts.every(p => p.lines.length === 0);
148
}
149
150
equals(other: GhostTextReplacement): boolean {
151
return this.lineNumber === other.lineNumber &&
152
this.columnRange.equals(other.columnRange) &&
153
this.newLines.length === other.newLines.length &&
154
this.newLines.every((line, index) => line === other.newLines[index]) &&
155
this.additionalReservedLineCount === other.additionalReservedLineCount;
156
}
157
}
158
159
export type GhostTextOrReplacement = GhostText | GhostTextReplacement;
160
161
export function ghostTextsOrReplacementsEqual(a: readonly GhostTextOrReplacement[] | undefined, b: readonly GhostTextOrReplacement[] | undefined): boolean {
162
return equals(a, b, ghostTextOrReplacementEquals);
163
}
164
165
export function ghostTextOrReplacementEquals(a: GhostTextOrReplacement | undefined, b: GhostTextOrReplacement | undefined): boolean {
166
if (a === b) {
167
return true;
168
}
169
if (!a || !b) {
170
return false;
171
}
172
if (a instanceof GhostText && b instanceof GhostText) {
173
return a.equals(b);
174
}
175
if (a instanceof GhostTextReplacement && b instanceof GhostTextReplacement) {
176
return a.equals(b);
177
}
178
return false;
179
}
180
181