1- import { readFile , writeFile } from "node:fs/promises" ;
1+ import { createReadStream , createWriteStream } from "node:fs" ;
2+ import { rename } from "node:fs/promises" ;
3+ import { createInterface } from "node:readline" ;
24import { glob } from "glob" ;
35import type { Diff , FileDiff } from "../components/types" ;
46import { Git } from "./git" ;
@@ -93,63 +95,97 @@ export class FileEditor {
9395 }
9496
9597 /**
96- * Edit file content in a cat-like manner
98+ * Replace a random word in a line with a cat word
9799 */
98- private editFileContent ( content : string ) : string {
99- const words = content . match ( / \w + / g) ;
100- if ( ! words || words . length === 0 ) return content ;
100+ private replaceRandomWord ( line : string ) : {
101+ edited : string ;
102+ original : string ;
103+ replaced : boolean ;
104+ } {
105+ const words = line . match ( / \b \w + \b / g) ;
106+ if ( ! words || words . length === 0 ) {
107+ return { edited : line , original : line , replaced : false } ;
108+ }
101109
102- const targetWord = words [
103- Math . floor ( Math . random ( ) * words . length )
104- ] as string ;
110+ // Select random word to replace
111+ const targetIndex = Math . floor ( Math . random ( ) * words . length ) ;
112+ const targetWord = words [ targetIndex ] as string ;
113+
114+ // Select random cat word
105115 const catWord = this . catWords [
106116 Math . floor ( Math . random ( ) * this . catWords . length )
107117 ] as string ;
108118
109- // Replace up to 3 locations
110- let replacedContent = content ;
111- let replacementCount = 0 ;
112- const maxReplacements = 3 ;
113-
114- replacedContent = replacedContent . replace (
115- new RegExp ( `\\b${ targetWord } \\b` , "g" ) ,
116- ( match ) => {
117- if ( replacementCount < maxReplacements ) {
118- replacementCount ++ ;
119- return catWord ;
120- }
121- return match ;
122- } ,
123- ) ;
119+ // Replace only the first occurrence of the selected word
120+ let replacementDone = false ;
121+ const edited = line . replace ( new RegExp ( `\\b${ targetWord } \\b` ) , ( match ) => {
122+ if ( ! replacementDone ) {
123+ replacementDone = true ;
124+ return catWord ;
125+ }
126+ return match ;
127+ } ) ;
124128
125- return replacedContent ;
129+ return {
130+ edited,
131+ original : line ,
132+ replaced : edited !== line ,
133+ } ;
126134 }
127135
128136 /**
129- * Generate structured diff
137+ * Edit file efficiently using readline
130138 */
131- private generateDiffs ( original : string , edited : string ) : Diff [ ] {
132- const originalLines = original . split ( "\n" ) ;
133- const editedLines = edited . split ( "\n" ) ;
139+ private async editFileReadline ( filePath : string ) : Promise < { diffs : Diff [ ] } > {
140+ const tempPath = `${ filePath } .tmp` ;
134141 const diffs : Diff [ ] = [ ] ;
135142
136- const maxLines = Math . max ( originalLines . length , editedLines . length ) ;
137-
138- for ( let i = 0 ; i < maxLines ; i ++ ) {
139- const rowNumber = i + 1 ;
140- const originalLine = originalLines [ i ] || "" ;
141- const editedLine = editedLines [ i ] || "" ;
142-
143- if ( originalLine !== editedLine ) {
144- diffs . push ( {
145- rowNumber,
146- a : originalLine ,
147- b : editedLine ,
148- } ) ;
143+ let totalReplacements = 0 ;
144+ const maxReplacements = 3 ;
145+ const replacementProbability = 0.2 ; // 20% chance to edit a line
146+
147+ // Process file line by line
148+ const readStream = createReadStream ( filePath ) ;
149+ const writeStream = createWriteStream ( tempPath ) ;
150+ const rl = createInterface ( {
151+ input : readStream ,
152+ crlfDelay : Number . POSITIVE_INFINITY ,
153+ } ) ;
154+
155+ let lineNumber = 0 ;
156+ for await ( const line of rl ) {
157+ lineNumber ++ ;
158+ let editedLine = line ;
159+
160+ // Decide whether to edit this line
161+ if (
162+ totalReplacements < maxReplacements &&
163+ Math . random ( ) < replacementProbability
164+ ) {
165+ const result = this . replaceRandomWord ( line ) ;
166+
167+ if ( result . replaced ) {
168+ editedLine = result . edited ;
169+ totalReplacements ++ ;
170+
171+ diffs . push ( {
172+ rowNumber : lineNumber ,
173+ a : result . original ,
174+ b : editedLine ,
175+ } ) ;
176+ }
149177 }
178+
179+ writeStream . write ( `${ editedLine } \n` ) ;
150180 }
151181
152- return diffs ;
182+ writeStream . end ( ) ;
183+ await new Promise < void > ( ( resolve ) => writeStream . on ( "finish" , resolve ) ) ;
184+
185+ // Atomically replace original file
186+ await rename ( tempPath , filePath ) ;
187+
188+ return { diffs } ;
153189 }
154190
155191 /**
@@ -161,17 +197,9 @@ export class FileEditor {
161197 return null ;
162198 }
163199
164- // Read file content
165- const originalContent = await readFile ( filePath , "utf-8" ) ;
166-
167- // Edit
168- const editedContent = this . editFileContent ( originalContent ) ;
169-
170- // Write to file
171- await writeFile ( filePath , editedContent , "utf-8" ) ;
200+ // Edit file using readline for efficiency
201+ const { diffs } = await this . editFileReadline ( filePath ) ;
172202
173- // Generate structured diff
174- const diffs = this . generateDiffs ( originalContent , editedContent ) ;
175203 if ( diffs . length === 0 ) {
176204 return null ;
177205 }
0 commit comments