Skip to content

Commit 6ec21d8

Browse files
koki-developclaude
andcommitted
feat: Optimize file editing with streaming and per-line random word replacement
- Replace line-by-line word replacement with individual random word selection per line - Implement efficient streaming processing using readline for memory optimization - Add probabilistic editing (20% chance per line) with maximum 3 replacements - Use atomic file replacement with temporary files for safe editing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 9d61a3e commit 6ec21d8

File tree

1 file changed

+80
-52
lines changed

1 file changed

+80
-52
lines changed

src/lib/fileEditor.ts

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
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";
24
import { glob } from "glob";
35
import type { Diff, FileDiff } from "../components/types";
46
import { 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

Comments
 (0)