src/vs/editor/common/textModelEditSource.ts

264 LOC · 120 covered · 144 uncovered · 24 ranges · 1716 concepts · 3 introducers · 862 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- editStack.ts ×46
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 { sumBy } from '../../base/common/arrays.js';
7 > import { prefixedUuid } from '../../base/common/uuid.js';
8 > import { LineEdit } from './core/edits/lineEdit.js';
9 > import { BaseStringEdit } from './core/edits/stringEdit.js';
10 > import { StringText } from './core/text/abstractText.js';
11 > import { TextLength } from './core/text/textLength.js';
12 > import { ProviderId, VersionedExtensionId } from './languages.js';
13 >
14 > const privateSymbol = Symbol('TextModelEditSource');
15 >
16 > export class TextModelEditSource {
17 > constructor(
18 > public readonly metadata: ITextModelEditSourceMetadata, textModelEditSource.ts ×2
19 > _privateCtorGuard: typeof privateSymbol,
20 > ) { }
22 > public toString(): string {
23 return `${this.metadata.source}`;
24 }
26 > public getType(): string {
27 const metadata = this.metadata;
28 switch (metadata.source) {
29 case 'cursor':
30 return metadata.kind;
31 case 'inlineCompletionAccept':
32 return metadata.source + (metadata.$nes ? ':nes' : '');
33 case 'unknown':
34 return metadata.name || 'unknown';
35 default:
36 return metadata.source;
37 }
38 }
40 > /**
41 > * Converts the metadata to a key string.
42 > * Only includes properties/values that have `level` many `$` prefixes or less.
43 > */
44 > public toKey(level: number, filter: { [TKey in ITextModelEditSourceMetadataKeys]?: boolean } = {}): string {
45 const metadata = this.metadata;
46 const keys = Object.entries(metadata).filter(([key, value]) => {
47 const filterVal = (filter as Record<string, boolean>)[key];
48 if (filterVal !== undefined) {
49 return filterVal;
50 }
51
52 const prefixCount = (key.match(/\$/g) || []).length;
53 return prefixCount <= level && value !== undefined && value !== null && value !== '';
54 }).map(([key, value]) => `${key}:${value}`);
55 return keys.join('-');
56 }
58 > public get props(): Record<ITextModelEditSourceMetadataKeys, string | undefined> {
59 // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
60 return this.metadata as any;
61 }
63 >
64 > type TextModelEditSourceT<T> = TextModelEditSource & {
65 > metadataT: T;
66 > };
67 >
68 > // eslint-disable-next-line @typescript-eslint/no-explicit-any
69 > function createEditSource<T extends Record<string, any>>(metadata: T): TextModelEditSourceT<T> { textModelEditSource.ts ×2
70 > // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
71 > return new TextModelEditSource(metadata as any, privateSymbol) as any;
72 > }
74 > export function isAiEdit(source: TextModelEditSource): boolean {
75 switch (source.metadata.source) {
76 case 'inlineCompletionAccept':
77 case 'inlineCompletionPartialAccept':
78 case 'inlineChat.applyEdits':
79 case 'Chat.applyEdits':
80 return true;
81 }
82 return false;
83 }
85 > export function isUserEdit(source: TextModelEditSource): boolean {
86 switch (source.metadata.source) {
87 case 'cursor':
88 return source.metadata.kind === 'type';
89 }
90 return false;
91 }
93 > export const EditSources = {
94 > unknown(data: { name?: string | null }) {
95 > return createEditSource({ editStack.ts ×15
96 > source: 'unknown',
97 > name: data.name,
98 > } as const);
99 > },
101 > rename: (oldName: string | undefined, newName: string) => createEditSource({ source: 'rename', $$$oldName: oldName, $$$newName: newName } as const),
102 >
103 > chatApplyEdits(data: {
104 modelId: string | undefined;
105 sessionId: string | undefined;
106 requestId: string | undefined;
107 languageId: string;
108 mode: string | undefined;
109 extensionId: VersionedExtensionId | undefined;
110 codeBlockSuggestionId: EditSuggestionId | undefined;
111 }) {
112 return createEditSource({
113 source: 'Chat.applyEdits',
114 $modelId: avoidPathRedaction(data.modelId),
115 $extensionId: data.extensionId?.extensionId,
116 $extensionVersion: data.extensionId?.version,
117 $$languageId: data.languageId,
118 $$sessionId: data.sessionId,
119 $$requestId: data.requestId,
120 $$mode: data.mode,
121 $$codeBlockSuggestionId: data.codeBlockSuggestionId,
122 } as const);
123 },
125 > chatUndoEdits: () => createEditSource({ source: 'Chat.undoEdits' } as const),
126 > chatReset: () => createEditSource({ source: 'Chat.reset' } as const),
127 >
128 > inlineCompletionAccept(data: { nes: boolean; requestUuid: string; languageId: string; providerId?: ProviderId; correlationId: string | undefined }) {
129 return createEditSource({
130 source: 'inlineCompletionAccept',
131 $nes: data.nes,
132 ...toProperties(data.providerId),
133 $$correlationId: data.correlationId,
134 $$requestUuid: data.requestUuid,
135 $$languageId: data.languageId,
136 } as const);
137 },
139 > inlineCompletionPartialAccept(data: { nes: boolean; requestUuid: string; languageId: string; providerId?: ProviderId; correlationId: string | undefined; type: 'word' | 'line' }) {
140 return createEditSource({
141 source: 'inlineCompletionPartialAccept',
142 type: data.type,
143 $nes: data.nes,
144 ...toProperties(data.providerId),
145 $$correlationId: data.correlationId,
146 $$requestUuid: data.requestUuid,
147 $$languageId: data.languageId,
148 } as const);
149 },
151 > inlineChatApplyEdit(data: { modelId: string | undefined; requestId: string | undefined; sessionId: string | undefined; languageId: string; extensionId: VersionedExtensionId | undefined }) {
152 return createEditSource({
153 source: 'inlineChat.applyEdits',
154 $modelId: avoidPathRedaction(data.modelId),
155 $extensionId: data.extensionId?.extensionId,
156 $extensionVersion: data.extensionId?.version,
157 $$sessionId: data.sessionId,
158 $$requestId: data.requestId,
159 $$languageId: data.languageId,
160 } as const);
161 },
163 > reloadFromDisk: () => createEditSource({ source: 'reloadFromDisk' } as const),
164 >
165 > cursor(data: { kind: 'compositionType' | 'compositionEnd' | 'type' | 'paste' | 'cut' | 'executeCommands' | 'executeCommand'; detailedSource?: string | null }) {
166 return createEditSource({
167 source: 'cursor',
168 kind: data.kind,
169 detailedSource: data.detailedSource,
170 } as const);
171 },
173 > setValue: () => createEditSource({ source: 'setValue' } as const),
174 > eolChange: () => createEditSource({ source: 'eolChange' } as const),
175 > applyEdits: () => createEditSource({ source: 'applyEdits' } as const),
176 > snippet: () => createEditSource({ source: 'snippet' } as const),
177 > suggest: (data: { providerId: ProviderId | undefined }) => createEditSource({ source: 'suggest', ...toProperties(data.providerId) } as const),
178 >
179 > codeAction: (data: { kind: string | undefined; providerId: ProviderId | undefined }) => createEditSource({ source: 'codeAction', $kind: data.kind, ...toProperties(data.providerId) } as const)
180 > };
181 >
182 function toProperties(version: ProviderId | undefined) {
183 if (!version) {
184 return {};
185 }
186 return {
187 $extensionId: version.extensionId,
188 $extensionVersion: version.extensionVersion,
189 $providerId: version.providerId,
190 };
191 }
193 > type Values<T> = T[keyof T];
194 > export type ITextModelEditSourceMetadata = Values<{ [TKey in keyof typeof EditSources]: ReturnType<typeof EditSources[TKey]>['metadataT'] }>;
195 > type ITextModelEditSourceMetadataKeys = Values<{ [TKey in keyof typeof EditSources]: keyof ReturnType<typeof EditSources[TKey]>['metadataT'] }>;
196 >
197 >
198 function avoidPathRedaction(str: string | undefined): string | undefined {
199 if (str === undefined) {
200 return undefined;
201 }
202 // To avoid false-positive file path redaction.
203 return str.replaceAll('/', '|');
204 }
206 >
207 > export class EditDeltaInfo {
208 > public static fromText(text: string): EditDeltaInfo {
209 > const linesAdded = TextLength.ofText(text).lineCount;
210 > const charsAdded = text.length;
211 > return new EditDeltaInfo(linesAdded, 0, charsAdded, 0);
212 > }
213 >
214 > /** @internal */
215 > public static fromEdit(edit: BaseStringEdit, originalString: StringText): EditDeltaInfo {
216 const lineEdit = LineEdit.fromStringEdit(edit, originalString);
217 const linesAdded = sumBy(lineEdit.replacements, r => r.newLines.length);
218 const linesRemoved = sumBy(lineEdit.replacements, r => r.lineRange.length);
219 const charsAdded = sumBy(edit.replacements, r => r.getNewLength());
220 const charsRemoved = sumBy(edit.replacements, r => r.replaceRange.length);
221 return new EditDeltaInfo(linesAdded, linesRemoved, charsAdded, charsRemoved);
222 }
224 > public static tryCreate(
225 linesAdded: number | undefined,
226 linesRemoved: number | undefined,
227 charsAdded: number | undefined,
228 charsRemoved: number | undefined
229 ): EditDeltaInfo | undefined {
230 if (linesAdded === undefined || linesRemoved === undefined || charsAdded === undefined || charsRemoved === undefined) {
231 return undefined;
232 }
233 return new EditDeltaInfo(linesAdded, linesRemoved, charsAdded, charsRemoved);
234 }
236 > constructor(
237 public readonly linesAdded: number,
238 public readonly linesRemoved: number,
239 public readonly charsAdded: number,
240 public readonly charsRemoved: number
241 ) { }
243 >
244 >
245 > /**
246 > * This is an opaque serializable type that represents a unique identity for an edit.
247 > */
248 > export interface EditSuggestionId {
249 > readonly _brand: 'EditIdentity';
250 > }
251 >
252 > export namespace EditSuggestionId {
253 > /**
254 > * Use AiEditTelemetryServiceImpl to create a new id!
255 > */
256 > export function newId(genPrefixedUuid?: (ns: string) => string): EditSuggestionId {
257 const id = genPrefixedUuid ? genPrefixedUuid('sgt') : prefixedUuid('sgt');
258 return toEditIdentity(id);
259 }
261 >
262 function toEditIdentity(id: string): EditSuggestionId {
263 return id as unknown as EditSuggestionId;
264 }