Path: blob/main/src/vs/editor/common/diff/legacyLinesDiffComputer.ts
3294 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 { CharCode } from '../../../base/common/charCode.js';6import { IDiffChange, ISequence, LcsDiff, IDiffResult } from '../../../base/common/diff/diff.js';7import { ILinesDiffComputer, ILinesDiffComputerOptions, LinesDiff } from './linesDiffComputer.js';8import { RangeMapping, DetailedLineRangeMapping } from './rangeMapping.js';9import * as strings from '../../../base/common/strings.js';10import { Range } from '../core/range.js';11import { assertFn, checkAdjacentItems } from '../../../base/common/assert.js';12import { LineRange } from '../core/ranges/lineRange.js';1314const MINIMUM_MATCHING_CHARACTER_LENGTH = 3;1516export class LegacyLinesDiffComputer implements ILinesDiffComputer {17computeDiff(originalLines: string[], modifiedLines: string[], options: ILinesDiffComputerOptions): LinesDiff {18const diffComputer = new DiffComputer(originalLines, modifiedLines, {19maxComputationTime: options.maxComputationTimeMs,20shouldIgnoreTrimWhitespace: options.ignoreTrimWhitespace,21shouldComputeCharChanges: true,22shouldMakePrettyDiff: true,23shouldPostProcessCharChanges: true,24});25const result = diffComputer.computeDiff();26const changes: DetailedLineRangeMapping[] = [];27let lastChange: DetailedLineRangeMapping | null = null;282930for (const c of result.changes) {31let originalRange: LineRange;32if (c.originalEndLineNumber === 0) {33// Insertion34originalRange = new LineRange(c.originalStartLineNumber + 1, c.originalStartLineNumber + 1);35} else {36originalRange = new LineRange(c.originalStartLineNumber, c.originalEndLineNumber + 1);37}3839let modifiedRange: LineRange;40if (c.modifiedEndLineNumber === 0) {41// Deletion42modifiedRange = new LineRange(c.modifiedStartLineNumber + 1, c.modifiedStartLineNumber + 1);43} else {44modifiedRange = new LineRange(c.modifiedStartLineNumber, c.modifiedEndLineNumber + 1);45}4647let change = new DetailedLineRangeMapping(originalRange, modifiedRange, c.charChanges?.map(c => new RangeMapping(48new Range(c.originalStartLineNumber, c.originalStartColumn, c.originalEndLineNumber, c.originalEndColumn),49new Range(c.modifiedStartLineNumber, c.modifiedStartColumn, c.modifiedEndLineNumber, c.modifiedEndColumn),50)));51if (lastChange) {52if (lastChange.modified.endLineNumberExclusive === change.modified.startLineNumber53|| lastChange.original.endLineNumberExclusive === change.original.startLineNumber) {54// join touching diffs. Probably moving diffs up/down in the algorithm causes touching diffs.55change = new DetailedLineRangeMapping(56lastChange.original.join(change.original),57lastChange.modified.join(change.modified),58lastChange.innerChanges && change.innerChanges ?59lastChange.innerChanges.concat(change.innerChanges) : undefined60);61changes.pop();62}63}6465changes.push(change);66lastChange = change;67}6869assertFn(() => {70return checkAdjacentItems(changes,71(m1, m2) => m2.original.startLineNumber - m1.original.endLineNumberExclusive === m2.modified.startLineNumber - m1.modified.endLineNumberExclusive &&72// There has to be an unchanged line in between (otherwise both diffs should have been joined)73m1.original.endLineNumberExclusive < m2.original.startLineNumber &&74m1.modified.endLineNumberExclusive < m2.modified.startLineNumber,75);76});7778return new LinesDiff(changes, [], result.quitEarly);79}80}8182export interface IDiffComputationResult {83quitEarly: boolean;84identical: boolean;8586/**87* The changes as (legacy) line change array.88* @deprecated Use `changes2` instead.89*/90changes: ILineChange[];9192/**93* The changes as (modern) line range mapping array.94*/95changes2: readonly DetailedLineRangeMapping[];96}9798/**99* A change100*/101export interface IChange {102readonly originalStartLineNumber: number;103readonly originalEndLineNumber: number;104readonly modifiedStartLineNumber: number;105readonly modifiedEndLineNumber: number;106}107108/**109* A character level change.110*/111export interface ICharChange extends IChange {112readonly originalStartColumn: number;113readonly originalEndColumn: number;114readonly modifiedStartColumn: number;115readonly modifiedEndColumn: number;116}117118/**119* A line change120*/121export interface ILineChange extends IChange {122readonly charChanges: ICharChange[] | undefined;123}124125export interface IDiffComputerResult {126quitEarly: boolean;127changes: ILineChange[];128}129130function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffResult {131const diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate);132return diffAlgo.ComputeDiff(pretty);133}134135class LineSequence implements ISequence {136137public readonly lines: string[];138private readonly _startColumns: number[];139private readonly _endColumns: number[];140141constructor(lines: string[]) {142const startColumns: number[] = [];143const endColumns: number[] = [];144for (let i = 0, length = lines.length; i < length; i++) {145startColumns[i] = getFirstNonBlankColumn(lines[i], 1);146endColumns[i] = getLastNonBlankColumn(lines[i], 1);147}148this.lines = lines;149this._startColumns = startColumns;150this._endColumns = endColumns;151}152153public getElements(): Int32Array | number[] | string[] {154const elements: string[] = [];155for (let i = 0, len = this.lines.length; i < len; i++) {156elements[i] = this.lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);157}158return elements;159}160161public getStrictElement(index: number): string {162return this.lines[index];163}164165public getStartLineNumber(i: number): number {166return i + 1;167}168169public getEndLineNumber(i: number): number {170return i + 1;171}172173public createCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence {174const charCodes: number[] = [];175const lineNumbers: number[] = [];176const columns: number[] = [];177let len = 0;178for (let index = startIndex; index <= endIndex; index++) {179const lineContent = this.lines[index];180const startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1);181const endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1);182for (let col = startColumn; col < endColumn; col++) {183charCodes[len] = lineContent.charCodeAt(col - 1);184lineNumbers[len] = index + 1;185columns[len] = col;186len++;187}188if (!shouldIgnoreTrimWhitespace && index < endIndex) {189// Add \n if trim whitespace is not ignored190charCodes[len] = CharCode.LineFeed;191lineNumbers[len] = index + 1;192columns[len] = lineContent.length + 1;193len++;194}195}196return new CharSequence(charCodes, lineNumbers, columns);197}198}199200class CharSequence implements ISequence {201202private readonly _charCodes: number[];203private readonly _lineNumbers: number[];204private readonly _columns: number[];205206constructor(charCodes: number[], lineNumbers: number[], columns: number[]) {207this._charCodes = charCodes;208this._lineNumbers = lineNumbers;209this._columns = columns;210}211212public toString() {213return (214'[' + this._charCodes.map((s, idx) => (s === CharCode.LineFeed ? '\\n' : String.fromCharCode(s)) + `-(${this._lineNumbers[idx]},${this._columns[idx]})`).join(', ') + ']'215);216}217218private _assertIndex(index: number, arr: number[]): void {219if (index < 0 || index >= arr.length) {220throw new Error(`Illegal index`);221}222}223224public getElements(): Int32Array | number[] | string[] {225return this._charCodes;226}227228public getStartLineNumber(i: number): number {229if (i > 0 && i === this._lineNumbers.length) {230// the start line number of the element after the last element231// is the end line number of the last element232return this.getEndLineNumber(i - 1);233}234this._assertIndex(i, this._lineNumbers);235236return this._lineNumbers[i];237}238239public getEndLineNumber(i: number): number {240if (i === -1) {241// the end line number of the element before the first element242// is the start line number of the first element243return this.getStartLineNumber(i + 1);244}245this._assertIndex(i, this._lineNumbers);246247if (this._charCodes[i] === CharCode.LineFeed) {248return this._lineNumbers[i] + 1;249}250return this._lineNumbers[i];251}252253public getStartColumn(i: number): number {254if (i > 0 && i === this._columns.length) {255// the start column of the element after the last element256// is the end column of the last element257return this.getEndColumn(i - 1);258}259this._assertIndex(i, this._columns);260return this._columns[i];261}262263public getEndColumn(i: number): number {264if (i === -1) {265// the end column of the element before the first element266// is the start column of the first element267return this.getStartColumn(i + 1);268}269this._assertIndex(i, this._columns);270271if (this._charCodes[i] === CharCode.LineFeed) {272return 1;273}274return this._columns[i] + 1;275}276}277278class CharChange implements ICharChange {279280public originalStartLineNumber: number;281public originalStartColumn: number;282public originalEndLineNumber: number;283public originalEndColumn: number;284285public modifiedStartLineNumber: number;286public modifiedStartColumn: number;287public modifiedEndLineNumber: number;288public modifiedEndColumn: number;289290constructor(291originalStartLineNumber: number,292originalStartColumn: number,293originalEndLineNumber: number,294originalEndColumn: number,295modifiedStartLineNumber: number,296modifiedStartColumn: number,297modifiedEndLineNumber: number,298modifiedEndColumn: number299) {300this.originalStartLineNumber = originalStartLineNumber;301this.originalStartColumn = originalStartColumn;302this.originalEndLineNumber = originalEndLineNumber;303this.originalEndColumn = originalEndColumn;304this.modifiedStartLineNumber = modifiedStartLineNumber;305this.modifiedStartColumn = modifiedStartColumn;306this.modifiedEndLineNumber = modifiedEndLineNumber;307this.modifiedEndColumn = modifiedEndColumn;308}309310public static createFromDiffChange(diffChange: IDiffChange, originalCharSequence: CharSequence, modifiedCharSequence: CharSequence): CharChange {311const originalStartLineNumber = originalCharSequence.getStartLineNumber(diffChange.originalStart);312const originalStartColumn = originalCharSequence.getStartColumn(diffChange.originalStart);313const originalEndLineNumber = originalCharSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);314const originalEndColumn = originalCharSequence.getEndColumn(diffChange.originalStart + diffChange.originalLength - 1);315316const modifiedStartLineNumber = modifiedCharSequence.getStartLineNumber(diffChange.modifiedStart);317const modifiedStartColumn = modifiedCharSequence.getStartColumn(diffChange.modifiedStart);318const modifiedEndLineNumber = modifiedCharSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);319const modifiedEndColumn = modifiedCharSequence.getEndColumn(diffChange.modifiedStart + diffChange.modifiedLength - 1);320321return new CharChange(322originalStartLineNumber, originalStartColumn, originalEndLineNumber, originalEndColumn,323modifiedStartLineNumber, modifiedStartColumn, modifiedEndLineNumber, modifiedEndColumn,324);325}326}327328function postProcessCharChanges(rawChanges: IDiffChange[]): IDiffChange[] {329if (rawChanges.length <= 1) {330return rawChanges;331}332333const result = [rawChanges[0]];334let prevChange = result[0];335336for (let i = 1, len = rawChanges.length; i < len; i++) {337const currChange = rawChanges[i];338339const originalMatchingLength = currChange.originalStart - (prevChange.originalStart + prevChange.originalLength);340const modifiedMatchingLength = currChange.modifiedStart - (prevChange.modifiedStart + prevChange.modifiedLength);341// Both of the above should be equal, but the continueProcessingPredicate may prevent this from being true342const matchingLength = Math.min(originalMatchingLength, modifiedMatchingLength);343344if (matchingLength < MINIMUM_MATCHING_CHARACTER_LENGTH) {345// Merge the current change into the previous one346prevChange.originalLength = (currChange.originalStart + currChange.originalLength) - prevChange.originalStart;347prevChange.modifiedLength = (currChange.modifiedStart + currChange.modifiedLength) - prevChange.modifiedStart;348} else {349// Add the current change350result.push(currChange);351prevChange = currChange;352}353}354355return result;356}357358class LineChange implements ILineChange {359public originalStartLineNumber: number;360public originalEndLineNumber: number;361public modifiedStartLineNumber: number;362public modifiedEndLineNumber: number;363public charChanges: CharChange[] | undefined;364365constructor(366originalStartLineNumber: number,367originalEndLineNumber: number,368modifiedStartLineNumber: number,369modifiedEndLineNumber: number,370charChanges: CharChange[] | undefined371) {372this.originalStartLineNumber = originalStartLineNumber;373this.originalEndLineNumber = originalEndLineNumber;374this.modifiedStartLineNumber = modifiedStartLineNumber;375this.modifiedEndLineNumber = modifiedEndLineNumber;376this.charChanges = charChanges;377}378379public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineSequence, modifiedLineSequence: LineSequence, continueCharDiff: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange {380let originalStartLineNumber: number;381let originalEndLineNumber: number;382let modifiedStartLineNumber: number;383let modifiedEndLineNumber: number;384let charChanges: CharChange[] | undefined = undefined;385386if (diffChange.originalLength === 0) {387originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart) - 1;388originalEndLineNumber = 0;389} else {390originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart);391originalEndLineNumber = originalLineSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);392}393394if (diffChange.modifiedLength === 0) {395modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart) - 1;396modifiedEndLineNumber = 0;397} else {398modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart);399modifiedEndLineNumber = modifiedLineSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);400}401402if (shouldComputeCharChanges && diffChange.originalLength > 0 && diffChange.originalLength < 20 && diffChange.modifiedLength > 0 && diffChange.modifiedLength < 20 && continueCharDiff()) {403// Compute character changes for diff chunks of at most 20 lines...404const originalCharSequence = originalLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);405const modifiedCharSequence = modifiedLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);406407if (originalCharSequence.getElements().length > 0 && modifiedCharSequence.getElements().length > 0) {408let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueCharDiff, true).changes;409410if (shouldPostProcessCharChanges) {411rawChanges = postProcessCharChanges(rawChanges);412}413414charChanges = [];415for (let i = 0, length = rawChanges.length; i < length; i++) {416charChanges.push(CharChange.createFromDiffChange(rawChanges[i], originalCharSequence, modifiedCharSequence));417}418}419}420421return new LineChange(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges);422}423}424425export interface IDiffComputerOpts {426shouldComputeCharChanges: boolean;427shouldPostProcessCharChanges: boolean;428shouldIgnoreTrimWhitespace: boolean;429shouldMakePrettyDiff: boolean;430maxComputationTime: number;431}432433export class DiffComputer {434435private readonly shouldComputeCharChanges: boolean;436private readonly shouldPostProcessCharChanges: boolean;437private readonly shouldIgnoreTrimWhitespace: boolean;438private readonly shouldMakePrettyDiff: boolean;439private readonly originalLines: string[];440private readonly modifiedLines: string[];441private readonly original: LineSequence;442private readonly modified: LineSequence;443private readonly continueLineDiff: () => boolean;444private readonly continueCharDiff: () => boolean;445446constructor(originalLines: string[], modifiedLines: string[], opts: IDiffComputerOpts) {447this.shouldComputeCharChanges = opts.shouldComputeCharChanges;448this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges;449this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace;450this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff;451this.originalLines = originalLines;452this.modifiedLines = modifiedLines;453this.original = new LineSequence(originalLines);454this.modified = new LineSequence(modifiedLines);455456this.continueLineDiff = createContinueProcessingPredicate(opts.maxComputationTime);457this.continueCharDiff = createContinueProcessingPredicate(opts.maxComputationTime === 0 ? 0 : Math.min(opts.maxComputationTime, 5000)); // never run after 5s for character changes...458}459460public computeDiff(): IDiffComputerResult {461462if (this.original.lines.length === 1 && this.original.lines[0].length === 0) {463// empty original => fast path464if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {465return {466quitEarly: false,467changes: []468};469}470471return {472quitEarly: false,473changes: [{474originalStartLineNumber: 1,475originalEndLineNumber: 1,476modifiedStartLineNumber: 1,477modifiedEndLineNumber: this.modified.lines.length,478charChanges: undefined479}]480};481}482483if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {484// empty modified => fast path485return {486quitEarly: false,487changes: [{488originalStartLineNumber: 1,489originalEndLineNumber: this.original.lines.length,490modifiedStartLineNumber: 1,491modifiedEndLineNumber: 1,492charChanges: undefined493}]494};495}496497const diffResult = computeDiff(this.original, this.modified, this.continueLineDiff, this.shouldMakePrettyDiff);498const rawChanges = diffResult.changes;499const quitEarly = diffResult.quitEarly;500501// The diff is always computed with ignoring trim whitespace502// This ensures we get the prettiest diff503504if (this.shouldIgnoreTrimWhitespace) {505const lineChanges: LineChange[] = [];506for (let i = 0, length = rawChanges.length; i < length; i++) {507lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));508}509return {510quitEarly: quitEarly,511changes: lineChanges512};513}514515// Need to post-process and introduce changes where the trim whitespace is different516// Note that we are looping starting at -1 to also cover the lines before the first change517const result: LineChange[] = [];518519let originalLineIndex = 0;520let modifiedLineIndex = 0;521for (let i = -1 /* !!!! */, len = rawChanges.length; i < len; i++) {522const nextChange = (i + 1 < len ? rawChanges[i + 1] : null);523const originalStop = (nextChange ? nextChange.originalStart : this.originalLines.length);524const modifiedStop = (nextChange ? nextChange.modifiedStart : this.modifiedLines.length);525526while (originalLineIndex < originalStop && modifiedLineIndex < modifiedStop) {527const originalLine = this.originalLines[originalLineIndex];528const modifiedLine = this.modifiedLines[modifiedLineIndex];529530if (originalLine !== modifiedLine) {531// These lines differ only in trim whitespace532533// Check the leading whitespace534{535let originalStartColumn = getFirstNonBlankColumn(originalLine, 1);536let modifiedStartColumn = getFirstNonBlankColumn(modifiedLine, 1);537while (originalStartColumn > 1 && modifiedStartColumn > 1) {538const originalChar = originalLine.charCodeAt(originalStartColumn - 2);539const modifiedChar = modifiedLine.charCodeAt(modifiedStartColumn - 2);540if (originalChar !== modifiedChar) {541break;542}543originalStartColumn--;544modifiedStartColumn--;545}546547if (originalStartColumn > 1 || modifiedStartColumn > 1) {548this._pushTrimWhitespaceCharChange(result,549originalLineIndex + 1, 1, originalStartColumn,550modifiedLineIndex + 1, 1, modifiedStartColumn551);552}553}554555// Check the trailing whitespace556{557let originalEndColumn = getLastNonBlankColumn(originalLine, 1);558let modifiedEndColumn = getLastNonBlankColumn(modifiedLine, 1);559const originalMaxColumn = originalLine.length + 1;560const modifiedMaxColumn = modifiedLine.length + 1;561while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) {562const originalChar = originalLine.charCodeAt(originalEndColumn - 1);563const modifiedChar = originalLine.charCodeAt(modifiedEndColumn - 1);564if (originalChar !== modifiedChar) {565break;566}567originalEndColumn++;568modifiedEndColumn++;569}570571if (originalEndColumn < originalMaxColumn || modifiedEndColumn < modifiedMaxColumn) {572this._pushTrimWhitespaceCharChange(result,573originalLineIndex + 1, originalEndColumn, originalMaxColumn,574modifiedLineIndex + 1, modifiedEndColumn, modifiedMaxColumn575);576}577}578}579originalLineIndex++;580modifiedLineIndex++;581}582583if (nextChange) {584// Emit the actual change585result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));586587originalLineIndex += nextChange.originalLength;588modifiedLineIndex += nextChange.modifiedLength;589}590}591592return {593quitEarly: quitEarly,594changes: result595};596}597598private _pushTrimWhitespaceCharChange(599result: LineChange[],600originalLineNumber: number, originalStartColumn: number, originalEndColumn: number,601modifiedLineNumber: number, modifiedStartColumn: number, modifiedEndColumn: number602): void {603if (this._mergeTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn)) {604// Merged into previous605return;606}607608let charChanges: CharChange[] | undefined = undefined;609if (this.shouldComputeCharChanges) {610charChanges = [new CharChange(611originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,612modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn613)];614}615result.push(new LineChange(616originalLineNumber, originalLineNumber,617modifiedLineNumber, modifiedLineNumber,618charChanges619));620}621622private _mergeTrimWhitespaceCharChange(623result: LineChange[],624originalLineNumber: number, originalStartColumn: number, originalEndColumn: number,625modifiedLineNumber: number, modifiedStartColumn: number, modifiedEndColumn: number626): boolean {627const len = result.length;628if (len === 0) {629return false;630}631632const prevChange = result[len - 1];633634if (prevChange.originalEndLineNumber === 0 || prevChange.modifiedEndLineNumber === 0) {635// Don't merge with inserts/deletes636return false;637}638639if (prevChange.originalEndLineNumber === originalLineNumber && prevChange.modifiedEndLineNumber === modifiedLineNumber) {640if (this.shouldComputeCharChanges && prevChange.charChanges) {641prevChange.charChanges.push(new CharChange(642originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,643modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn644));645}646return true;647}648649if (prevChange.originalEndLineNumber + 1 === originalLineNumber && prevChange.modifiedEndLineNumber + 1 === modifiedLineNumber) {650prevChange.originalEndLineNumber = originalLineNumber;651prevChange.modifiedEndLineNumber = modifiedLineNumber;652if (this.shouldComputeCharChanges && prevChange.charChanges) {653prevChange.charChanges.push(new CharChange(654originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,655modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn656));657}658return true;659}660661return false;662}663}664665function getFirstNonBlankColumn(txt: string, defaultValue: number): number {666const r = strings.firstNonWhitespaceIndex(txt);667if (r === -1) {668return defaultValue;669}670return r + 1;671}672673function getLastNonBlankColumn(txt: string, defaultValue: number): number {674const r = strings.lastNonWhitespaceIndex(txt);675if (r === -1) {676return defaultValue;677}678return r + 2;679}680681function createContinueProcessingPredicate(maximumRuntime: number): () => boolean {682if (maximumRuntime === 0) {683return () => true;684}685686const startTime = Date.now();687return () => {688return Date.now() - startTime < maximumRuntime;689};690}691692693