Path: blob/main/extensions/copilot/src/extension/inlineEdits/common/informationDelta.tsx
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { StringEdit } from '../../../util/vs/editor/common/core/edits/stringEdit';6import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange';78const N_GRAM_UNDO_RATIO_TO_FILTER_OUT = 0.7;910/**11* Represents information loss/gain (4-grams) via an edit.12*/13export class InformationDelta {1415constructor(16public readonly inserted: Set<string> = new Set<string>(),17public readonly deleted: Set<string> = new Set<string>()18) { }1920combine(other: InformationDelta) {21return new InformationDelta(setUnion(this.inserted, other.inserted), setUnion(this.deleted, other.deleted));22}2324isUndoneBy(other: InformationDelta) {25const otherReallyNewInsertions = setMinus(other.inserted, other.deleted);26const otherReallyDeleted = setMinus(other.deleted, other.inserted);2728const otherReallyDeletesMyInserts = setIntersectionCount(otherReallyDeleted, this.inserted);29const otherReallyInsertsMyDeletes = setIntersectionCount(otherReallyNewInsertions, this.deleted);3031if (otherReallyDeleted.size > 6 && otherReallyDeletesMyInserts / otherReallyDeleted.size > N_GRAM_UNDO_RATIO_TO_FILTER_OUT) {32return true;33}3435if (otherReallyNewInsertions.size > 6 && otherReallyInsertsMyDeletes / otherReallyNewInsertions.size > N_GRAM_UNDO_RATIO_TO_FILTER_OUT) {36return true;37}3839return false;40}41}4243export function getInformationDelta(source: string, edit: StringEdit): InformationDelta {44const inserted = new Set<string>();45const deleted = new Set<string>();46const tryAddDeleted = (deletedRange: OffsetRange | undefined) => {47if (!deletedRange) {48return;49}50const deletedText = source.substring(deletedRange.start, deletedRange.endExclusive);51for (let line of deletedText.split(/\r\n|\r|\n/)) {52line = line.trim();53for (const piece of to4grams(line)) {54deleted.add(piece);55}56}57};58const tryAddInserted = (insertedText: string) => {59for (let line of insertedText.split(/\r\n|\r|\n/)) {60line = line.trim();61for (const piece of to4grams(line)) {62inserted.add(piece);63}64}65};66for (const e of edit.replacements) {67const e1 = e.removeCommonPrefix(source).removeCommonSuffix(source);68const e2 = e.removeCommonSuffix(source).removeCommonPrefix(source);69if (e1.isEmpty) {70continue;71}72tryAddDeleted(e1.replaceRange);73tryAddDeleted(e2.replaceRange);74tryAddDeleted(e1.replaceRange.intersect(e2.replaceRange));7576// tryAddInserted(e1.newText);77// tryAddInserted(e2.newText);78// e1 might have a suffix overlap with the prefix of e179tryAddInserted(trimOverlap(e1.newText, e2.newText));80}81return new InformationDelta(inserted, deleted);82}8384function trimOverlap(stringToEliminateEnd: string, stringToEliminateStart: string): string {85const length = Math.min(stringToEliminateEnd.length, stringToEliminateStart.length);86for (let trimLength = 0; trimLength < length; trimLength++) {87const str1 = stringToEliminateEnd.slice(0, stringToEliminateEnd.length - trimLength);88const str2 = stringToEliminateStart.slice(trimLength);89if (str1 === str2) {90return str1;91}92}93return '';94}9596function to4grams(text: string) {97const result: string[] = [];98for (let i = 4; i < text.length; i++) {99const ngram = text.slice(i - 4, i);100result.push(ngram);101}102return result;103}104105function setUnion(a: Set<string>, b: Set<string>): Set<string> {106const result = new Set<string>();107for (const el of a) {108result.add(el);109}110for (const el of b) {111result.add(el);112}113return result;114}115116function setMinus(a: Set<string>, b: Set<string>): Set<string> {117const result = new Set<string>();118for (const el of a) {119if (!b.has(el)) {120result.add(el);121}122}123return result;124}125126function setIntersectionCount(a: Set<string>, b: Set<string>): number {127let result = 0;128for (const el of a) {129if (b.has(el)) {130result++;131}132}133return result;134}135136137