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.
/*---------------------------------------------------------------------------------------------
editStack.ts ×46
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { sumBy } from '../../base/common/arrays.js';
import { prefixedUuid } from '../../base/common/uuid.js';
import { LineEdit } from './core/edits/lineEdit.js';
import { BaseStringEdit } from './core/edits/stringEdit.js';
import { StringText } from './core/text/abstractText.js';
import { TextLength } from './core/text/textLength.js';
import { ProviderId, VersionedExtensionId } from './languages.js';
const privateSymbol = Symbol('TextModelEditSource');
export class TextModelEditSource {
constructor(
_privateCtorGuard: typeof privateSymbol,
) { }
public toString(): string {
return `${this.metadata.source}`;
}
public getType(): string {
const metadata = this.metadata;
switch (metadata.source) {
case 'cursor':
return metadata.kind;
case 'inlineCompletionAccept':
return metadata.source + (metadata.$nes ? ':nes' : '');
case 'unknown':
return metadata.name || 'unknown';
default:
return metadata.source;
}
}
/**
* Converts the metadata to a key string.
* Only includes properties/values that have `level` many `$` prefixes or less.
*/
public toKey(level: number, filter: { [TKey in ITextModelEditSourceMetadataKeys]?: boolean } = {}): string {
const metadata = this.metadata;
const keys = Object.entries(metadata).filter(([key, value]) => {
const filterVal = (filter as Record<string, boolean>)[key];
if (filterVal !== undefined) {
return filterVal;
}
const prefixCount = (key.match(/\$/g) || []).length;
return prefixCount <= level && value !== undefined && value !== null && value !== '';
}).map(([key, value]) => `${key}:${value}`);
return keys.join('-');
}
public get props(): Record<ITextModelEditSourceMetadataKeys, string | undefined> {
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
return this.metadata as any;
}
type TextModelEditSourceT<T> = TextModelEditSource & {
metadataT: T;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createEditSource<T extends Record<string, any>>(metadata: T): TextModelEditSourceT<T> {
textModelEditSource.ts ×2
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
return new TextModelEditSource(metadata as any, privateSymbol) as any;
}
export function isAiEdit(source: TextModelEditSource): boolean {
switch (source.metadata.source) {
case 'inlineCompletionAccept':
case 'inlineCompletionPartialAccept':
case 'inlineChat.applyEdits':
case 'Chat.applyEdits':
return true;
}
return false;
}
export function isUserEdit(source: TextModelEditSource): boolean {
switch (source.metadata.source) {
case 'cursor':
return source.metadata.kind === 'type';
}
return false;
}
export const EditSources = {
unknown(data: { name?: string | null }) {
source: 'unknown',
name: data.name,
} as const);
},
rename: (oldName: string | undefined, newName: string) => createEditSource({ source: 'rename', $$$oldName: oldName, $$$newName: newName } as const),
chatApplyEdits(data: {
modelId: string | undefined;
sessionId: string | undefined;
requestId: string | undefined;
languageId: string;
mode: string | undefined;
extensionId: VersionedExtensionId | undefined;
codeBlockSuggestionId: EditSuggestionId | undefined;
}) {
return createEditSource({
source: 'Chat.applyEdits',
$modelId: avoidPathRedaction(data.modelId),
$extensionId: data.extensionId?.extensionId,
$extensionVersion: data.extensionId?.version,
$$languageId: data.languageId,
$$sessionId: data.sessionId,
$$requestId: data.requestId,
$$mode: data.mode,
$$codeBlockSuggestionId: data.codeBlockSuggestionId,
} as const);
},
chatUndoEdits: () => createEditSource({ source: 'Chat.undoEdits' } as const),
chatReset: () => createEditSource({ source: 'Chat.reset' } as const),
inlineCompletionAccept(data: { nes: boolean; requestUuid: string; languageId: string; providerId?: ProviderId; correlationId: string | undefined }) {
return createEditSource({
source: 'inlineCompletionAccept',
$nes: data.nes,
...toProperties(data.providerId),
$$correlationId: data.correlationId,
$$requestUuid: data.requestUuid,
$$languageId: data.languageId,
} as const);
},
inlineCompletionPartialAccept(data: { nes: boolean; requestUuid: string; languageId: string; providerId?: ProviderId; correlationId: string | undefined; type: 'word' | 'line' }) {
return createEditSource({
source: 'inlineCompletionPartialAccept',
type: data.type,
$nes: data.nes,
...toProperties(data.providerId),
$$correlationId: data.correlationId,
$$requestUuid: data.requestUuid,
$$languageId: data.languageId,
} as const);
},
inlineChatApplyEdit(data: { modelId: string | undefined; requestId: string | undefined; sessionId: string | undefined; languageId: string; extensionId: VersionedExtensionId | undefined }) {
return createEditSource({
source: 'inlineChat.applyEdits',
$modelId: avoidPathRedaction(data.modelId),
$extensionId: data.extensionId?.extensionId,
$extensionVersion: data.extensionId?.version,
$$sessionId: data.sessionId,
$$requestId: data.requestId,
$$languageId: data.languageId,
} as const);
},
reloadFromDisk: () => createEditSource({ source: 'reloadFromDisk' } as const),
cursor(data: { kind: 'compositionType' | 'compositionEnd' | 'type' | 'paste' | 'cut' | 'executeCommands' | 'executeCommand'; detailedSource?: string | null }) {
return createEditSource({
source: 'cursor',
kind: data.kind,
detailedSource: data.detailedSource,
} as const);
},
setValue: () => createEditSource({ source: 'setValue' } as const),
eolChange: () => createEditSource({ source: 'eolChange' } as const),
applyEdits: () => createEditSource({ source: 'applyEdits' } as const),
snippet: () => createEditSource({ source: 'snippet' } as const),
suggest: (data: { providerId: ProviderId | undefined }) => createEditSource({ source: 'suggest', ...toProperties(data.providerId) } as const),
codeAction: (data: { kind: string | undefined; providerId: ProviderId | undefined }) => createEditSource({ source: 'codeAction', $kind: data.kind, ...toProperties(data.providerId) } as const)
};
function toProperties(version: ProviderId | undefined) {
if (!version) {
return {};
}
return {
$extensionId: version.extensionId,
$extensionVersion: version.extensionVersion,
$providerId: version.providerId,
};
}
type Values<T> = T[keyof T];
export type ITextModelEditSourceMetadata = Values<{ [TKey in keyof typeof EditSources]: ReturnType<typeof EditSources[TKey]>['metadataT'] }>;
type ITextModelEditSourceMetadataKeys = Values<{ [TKey in keyof typeof EditSources]: keyof ReturnType<typeof EditSources[TKey]>['metadataT'] }>;
function avoidPathRedaction(str: string | undefined): string | undefined {
if (str === undefined) {
return undefined;
}
// To avoid false-positive file path redaction.
return str.replaceAll('/', '|');
}
export class EditDeltaInfo {
public static fromText(text: string): EditDeltaInfo {
const linesAdded = TextLength.ofText(text).lineCount;
const charsAdded = text.length;
return new EditDeltaInfo(linesAdded, 0, charsAdded, 0);
}
/** @internal */
public static fromEdit(edit: BaseStringEdit, originalString: StringText): EditDeltaInfo {
const lineEdit = LineEdit.fromStringEdit(edit, originalString);
const linesAdded = sumBy(lineEdit.replacements, r => r.newLines.length);
const linesRemoved = sumBy(lineEdit.replacements, r => r.lineRange.length);
const charsAdded = sumBy(edit.replacements, r => r.getNewLength());
const charsRemoved = sumBy(edit.replacements, r => r.replaceRange.length);
return new EditDeltaInfo(linesAdded, linesRemoved, charsAdded, charsRemoved);
}
public static tryCreate(
linesAdded: number | undefined,
linesRemoved: number | undefined,
charsAdded: number | undefined,
charsRemoved: number | undefined
): EditDeltaInfo | undefined {
if (linesAdded === undefined || linesRemoved === undefined || charsAdded === undefined || charsRemoved === undefined) {
return undefined;
}
return new EditDeltaInfo(linesAdded, linesRemoved, charsAdded, charsRemoved);
}
constructor(
public readonly linesAdded: number,
public readonly linesRemoved: number,
public readonly charsAdded: number,
public readonly charsRemoved: number
) { }
/**
* This is an opaque serializable type that represents a unique identity for an edit.
*/
export interface EditSuggestionId {
readonly _brand: 'EditIdentity';
}
export namespace EditSuggestionId {
/**
* Use AiEditTelemetryServiceImpl to create a new id!
*/
export function newId(genPrefixedUuid?: (ns: string) => string): EditSuggestionId {
const id = genPrefixedUuid ? genPrefixedUuid('sgt') : prefixedUuid('sgt');
return toEditIdentity(id);
}
function toEditIdentity(id: string): EditSuggestionId {
return id as unknown as EditSuggestionId;
}