fileditor-mcp 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/1.exe +0 -0
- package/1.json +12 -0
- package/LICENSE +21 -21
- package/README.md +128 -128
- package/docs/MCP-INTERFACE.cn.md +975 -1061
- package/docs/MCP-INTERFACE.en.md +881 -974
- package/docs/README.cn.md +128 -128
- package/package.json +30 -31
- package/src/handlers/applyDiff.js +298 -324
- package/src/handlers/insertContent.js +179 -153
- package/src/handlers/listFiles.js +65 -54
- package/src/handlers/readFile.js +109 -109
- package/src/handlers/searchAndReplace.js +198 -217
- package/src/handlers/setWorkspace.js +26 -26
- package/src/handlers/writeFile.js +97 -91
- package/src/index.js +22 -22
- package/src/server.js +85 -85
- package/src/tools/toolDefinitions.js +291 -301
- package/src/utils/fileUtils.js +238 -716
- package/test/ApplyDiffHandler.test.js +754 -754
- package/test/InsertContentHandler.test.js +371 -369
- package/test/ListFilesHandler.test.js +302 -300
- package/test/ReadFileHandler.test.js +213 -213
- package/test/SearchAndReplaceHandler.test.js +505 -505
- package/test/SetWorkspaceHandler.test.js +290 -290
- package/test/WriteFileHandler.test.js +289 -289
- package/test/runAllTests.js +233 -233
package/src/handlers/readFile.js
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* read_file tool handler
|
|
5
|
-
*/
|
|
6
|
-
export class ReadFileHandler {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Handle read_file request
|
|
10
|
-
* @param {Object} args - Request parameters
|
|
11
|
-
* @returns {Promise<Object>} Response result
|
|
12
|
-
*/
|
|
13
|
-
static async handle(args) {
|
|
14
|
-
const { path, line_range } = args;
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
// Check if single file or multiple files
|
|
18
|
-
if (Array.isArray(path)) {
|
|
19
|
-
// Read multiple files
|
|
20
|
-
return await ReadFileHandler.readMultipleFiles(path);
|
|
21
|
-
} else {
|
|
22
|
-
// Read single file
|
|
23
|
-
return await ReadFileHandler.readSingleFile(path, line_range);
|
|
24
|
-
}
|
|
25
|
-
} catch (error) {
|
|
26
|
-
throw new Error(`Failed to read file(s): ${error.message}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Read a single file
|
|
32
|
-
* @param {string} filePath - File path
|
|
33
|
-
* @param {string} line_range - Line range
|
|
34
|
-
* @returns {Promise<Object>} Response result
|
|
35
|
-
*/
|
|
36
|
-
static async readSingleFile(filePath, line_range) {
|
|
37
|
-
const content = await FileUtils.readFile(filePath);
|
|
38
|
-
|
|
39
|
-
if (line_range) {
|
|
40
|
-
const selectedContent = FileUtils.getLineRange(content, line_range);
|
|
41
|
-
const [startLine] = line_range.split('-').map(Number);
|
|
42
|
-
const formattedContent = FileUtils.formatWithLineNumbers(selectedContent, startLine);
|
|
43
|
-
return FileUtils.createResponse(formattedContent);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const formattedContent = FileUtils.formatWithLineNumbers(content);
|
|
47
|
-
return FileUtils.createResponse(formattedContent);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Read multiple files
|
|
52
|
-
* @param {string[]} filePaths - Array of file paths
|
|
53
|
-
* @returns {Promise<Object>} Response result
|
|
54
|
-
*/
|
|
55
|
-
static async readMultipleFiles(filePaths) {
|
|
56
|
-
// Check for empty array
|
|
57
|
-
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
|
58
|
-
throw new Error('Failed to read any files: No files provided');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const results = [];
|
|
62
|
-
const errors = [];
|
|
63
|
-
|
|
64
|
-
for (const filePath of filePaths) {
|
|
65
|
-
try {
|
|
66
|
-
if (!FileUtils.fileExists(filePath)) {
|
|
67
|
-
errors.push(`File not found: ${filePath}`);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const content = await FileUtils.readFile(filePath);
|
|
72
|
-
results.push({
|
|
73
|
-
file: filePath,
|
|
74
|
-
content: content,
|
|
75
|
-
lines: FileUtils.countLines(content)
|
|
76
|
-
});
|
|
77
|
-
} catch (error) {
|
|
78
|
-
errors.push(`Error reading ${filePath}: ${error.message}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Build return content
|
|
83
|
-
let responseText = '';
|
|
84
|
-
|
|
85
|
-
if (results.length > 0) {
|
|
86
|
-
responseText += `Successfully read ${results.length} file(s):\n\n`;
|
|
87
|
-
|
|
88
|
-
for (const result of results) {
|
|
89
|
-
responseText += `=== ${result.file} (${result.lines} lines) ===\n`;
|
|
90
|
-
const formattedContent = FileUtils.formatWithLineNumbers(result.content);
|
|
91
|
-
responseText += formattedContent;
|
|
92
|
-
responseText += '\n\n';
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (errors.length > 0) {
|
|
97
|
-
responseText += `Errors encountered:\n`;
|
|
98
|
-
for (const error of errors) {
|
|
99
|
-
responseText += `- ${error}\n`;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (results.length === 0 && errors.length > 0) {
|
|
104
|
-
throw new Error(`Failed to read any files: ${errors.join(', ')}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return FileUtils.createResponse(responseText.trim());
|
|
108
|
-
}
|
|
109
|
-
}
|
|
1
|
+
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* read_file tool handler
|
|
5
|
+
*/
|
|
6
|
+
export class ReadFileHandler {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle read_file request
|
|
10
|
+
* @param {Object} args - Request parameters
|
|
11
|
+
* @returns {Promise<Object>} Response result
|
|
12
|
+
*/
|
|
13
|
+
static async handle(args) {
|
|
14
|
+
const { path, line_range } = args;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Check if single file or multiple files
|
|
18
|
+
if (Array.isArray(path)) {
|
|
19
|
+
// Read multiple files
|
|
20
|
+
return await ReadFileHandler.readMultipleFiles(path);
|
|
21
|
+
} else {
|
|
22
|
+
// Read single file
|
|
23
|
+
return await ReadFileHandler.readSingleFile(path, line_range);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to read file(s): ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read a single file
|
|
32
|
+
* @param {string} filePath - File path
|
|
33
|
+
* @param {string} line_range - Line range
|
|
34
|
+
* @returns {Promise<Object>} Response result
|
|
35
|
+
*/
|
|
36
|
+
static async readSingleFile(filePath, line_range) {
|
|
37
|
+
const content = await FileUtils.readFile(filePath);
|
|
38
|
+
|
|
39
|
+
if (line_range) {
|
|
40
|
+
const selectedContent = FileUtils.getLineRange(content, line_range);
|
|
41
|
+
const [startLine] = line_range.split('-').map(Number);
|
|
42
|
+
const formattedContent = FileUtils.formatWithLineNumbers(selectedContent, startLine);
|
|
43
|
+
return FileUtils.createResponse(formattedContent);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const formattedContent = FileUtils.formatWithLineNumbers(content);
|
|
47
|
+
return FileUtils.createResponse(formattedContent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read multiple files
|
|
52
|
+
* @param {string[]} filePaths - Array of file paths
|
|
53
|
+
* @returns {Promise<Object>} Response result
|
|
54
|
+
*/
|
|
55
|
+
static async readMultipleFiles(filePaths) {
|
|
56
|
+
// Check for empty array
|
|
57
|
+
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
|
58
|
+
throw new Error('Failed to read any files: No files provided');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = [];
|
|
62
|
+
const errors = [];
|
|
63
|
+
|
|
64
|
+
for (const filePath of filePaths) {
|
|
65
|
+
try {
|
|
66
|
+
if (!FileUtils.fileExists(filePath)) {
|
|
67
|
+
errors.push(`File not found: ${filePath}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const content = await FileUtils.readFile(filePath);
|
|
72
|
+
results.push({
|
|
73
|
+
file: filePath,
|
|
74
|
+
content: content,
|
|
75
|
+
lines: FileUtils.countLines(content)
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
errors.push(`Error reading ${filePath}: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build return content
|
|
83
|
+
let responseText = '';
|
|
84
|
+
|
|
85
|
+
if (results.length > 0) {
|
|
86
|
+
responseText += `Successfully read ${results.length} file(s):\n\n`;
|
|
87
|
+
|
|
88
|
+
for (const result of results) {
|
|
89
|
+
responseText += `=== ${result.file} (${result.lines} lines) ===\n`;
|
|
90
|
+
const formattedContent = FileUtils.formatWithLineNumbers(result.content);
|
|
91
|
+
responseText += formattedContent;
|
|
92
|
+
responseText += '\n\n';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (errors.length > 0) {
|
|
97
|
+
responseText += `Errors encountered:\n`;
|
|
98
|
+
for (const error of errors) {
|
|
99
|
+
responseText += `- ${error}\n`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (results.length === 0 && errors.length > 0) {
|
|
104
|
+
throw new Error(`Failed to read any files: ${errors.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return FileUtils.createResponse(responseText.trim());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,217 +1,198 @@
|
|
|
1
|
-
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* search_and_replace tool handler
|
|
5
|
-
*/
|
|
6
|
-
export class SearchAndReplaceHandler {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Handle search_and_replace request
|
|
10
|
-
* @param {Object} args - Request parameters
|
|
11
|
-
* @returns {Promise<Object>} Response result
|
|
12
|
-
*/
|
|
13
|
-
static async handle(args) {
|
|
14
|
-
const {
|
|
15
|
-
path,
|
|
16
|
-
search,
|
|
17
|
-
replace,
|
|
18
|
-
use_regex = false,
|
|
19
|
-
ignore_case = false,
|
|
20
|
-
start_line = null,
|
|
21
|
-
end_line = null
|
|
22
|
-
} = args;
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// Check if file exists
|
|
26
|
-
if (!FileUtils.fileExists(path)) {
|
|
27
|
-
throw new Error(`File not found: ${path}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Read file content
|
|
31
|
-
const content = await FileUtils.readFile(path);
|
|
32
|
-
const lines = content.split('\n');
|
|
33
|
-
|
|
34
|
-
// Validate
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
*
|
|
167
|
-
* @param {string}
|
|
168
|
-
* @
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const rangeDesc = start_line && end_line
|
|
200
|
-
? `lines ${start_line}-${end_line}`
|
|
201
|
-
: start_line
|
|
202
|
-
? `from line ${start_line}`
|
|
203
|
-
: `up to line ${end_line}`;
|
|
204
|
-
message += ` (${rangeDesc})`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (use_regex) {
|
|
208
|
-
message += ` using regex pattern`;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (ignore_case) {
|
|
212
|
-
message += ` (case-insensitive)`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return message;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
1
|
+
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* search_and_replace tool handler
|
|
5
|
+
*/
|
|
6
|
+
export class SearchAndReplaceHandler {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle search_and_replace request
|
|
10
|
+
* @param {Object} args - Request parameters
|
|
11
|
+
* @returns {Promise<Object>} Response result
|
|
12
|
+
*/
|
|
13
|
+
static async handle(args) {
|
|
14
|
+
const {
|
|
15
|
+
path,
|
|
16
|
+
search,
|
|
17
|
+
replace,
|
|
18
|
+
use_regex = false,
|
|
19
|
+
ignore_case = false,
|
|
20
|
+
start_line = null,
|
|
21
|
+
end_line = null
|
|
22
|
+
} = args;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Check if file exists
|
|
26
|
+
if (!FileUtils.fileExists(path)) {
|
|
27
|
+
throw new Error(`File not found: ${path}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Read file content
|
|
31
|
+
const content = await FileUtils.readFile(path);
|
|
32
|
+
const lines = content.split('\n'); // Determine search range
|
|
33
|
+
const startIdx = start_line !== null ? Math.max(0, start_line - 1) : 0;
|
|
34
|
+
const endIdx = end_line !== null ? Math.min(lines.length, end_line) : lines.length; // Validate search string
|
|
35
|
+
if (!search) {
|
|
36
|
+
// Empty search string, do not perform any replacement
|
|
37
|
+
let message = `Successfully replaced 0 occurrence(s) in ${path}`;
|
|
38
|
+
if (start_line || end_line) {
|
|
39
|
+
const rangeDesc = start_line && end_line
|
|
40
|
+
? `lines ${start_line}-${end_line}`
|
|
41
|
+
: start_line
|
|
42
|
+
? `from line ${start_line}`
|
|
43
|
+
: `up to line ${end_line}`;
|
|
44
|
+
message += ` (${rangeDesc})`;
|
|
45
|
+
}
|
|
46
|
+
return FileUtils.createResponse(message);
|
|
47
|
+
}
|
|
48
|
+
if (start_line !== null && start_line < 1) {
|
|
49
|
+
throw new Error(`Invalid start_line: ${start_line}. Line numbers start from 1.`);
|
|
50
|
+
}
|
|
51
|
+
if (end_line !== null && end_line < 1) {
|
|
52
|
+
throw new Error(`Invalid end_line: ${end_line}. Line numbers start from 1.`);
|
|
53
|
+
} if (start_line !== null && end_line !== null && start_line > end_line) {
|
|
54
|
+
throw new Error(`start_line (${start_line}) cannot be greater than end_line (${end_line})`);
|
|
55
|
+
}
|
|
56
|
+
if (start_line !== null && start_line > lines.length) {
|
|
57
|
+
throw new Error(`start_line (${start_line}) exceeds file length (${lines.length} lines)`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Perform search and replace
|
|
61
|
+
let replacementCount = 0;
|
|
62
|
+
const searchLines = lines.slice(startIdx, endIdx);
|
|
63
|
+
const beforeLines = lines.slice(0, startIdx);
|
|
64
|
+
const afterLines = lines.slice(endIdx);
|
|
65
|
+
|
|
66
|
+
const processedLines = searchLines.map(line => {
|
|
67
|
+
let newLine;
|
|
68
|
+
|
|
69
|
+
if (use_regex) {
|
|
70
|
+
try {
|
|
71
|
+
// Build regex flags
|
|
72
|
+
let flags = 'g';
|
|
73
|
+
if (ignore_case) {
|
|
74
|
+
flags += 'i';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const regex = new RegExp(search, flags);
|
|
78
|
+
const matches = line.match(regex);
|
|
79
|
+
if (matches) {
|
|
80
|
+
replacementCount += matches.length;
|
|
81
|
+
}
|
|
82
|
+
newLine = line.replace(regex, replace);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Invalid regular expression: ${search}. ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// Simple string replacement
|
|
88
|
+
if (ignore_case) {
|
|
89
|
+
// Case-insensitive string replacement
|
|
90
|
+
const searchLower = search.toLowerCase();
|
|
91
|
+
const lineLower = line.toLowerCase();
|
|
92
|
+
|
|
93
|
+
// Count occurrences
|
|
94
|
+
const occurrences = SearchAndReplaceHandler.countOccurrencesCaseInsensitive(line, search);
|
|
95
|
+
replacementCount += occurrences;
|
|
96
|
+
|
|
97
|
+
// Perform replacement
|
|
98
|
+
newLine = SearchAndReplaceHandler.replaceAllCaseInsensitive(line, search, replace);
|
|
99
|
+
} else {
|
|
100
|
+
// Case-sensitive string replacement
|
|
101
|
+
const occurrences = SearchAndReplaceHandler.countOccurrences(line, search);
|
|
102
|
+
replacementCount += occurrences;
|
|
103
|
+
newLine = line.split(search).join(replace);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return newLine;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Rebuild file content
|
|
111
|
+
const newContent = [...beforeLines, ...processedLines, ...afterLines].join('\n');
|
|
112
|
+
|
|
113
|
+
// Write back to file
|
|
114
|
+
await FileUtils.writeFile(path, newContent);
|
|
115
|
+
|
|
116
|
+
// Build response message
|
|
117
|
+
let message = `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
|
|
118
|
+
|
|
119
|
+
if (start_line || end_line) {
|
|
120
|
+
const rangeDesc = start_line && end_line
|
|
121
|
+
? `lines ${start_line}-${end_line}`
|
|
122
|
+
: start_line
|
|
123
|
+
? `from line ${start_line}`
|
|
124
|
+
: `up to line ${end_line}`;
|
|
125
|
+
message += ` (${rangeDesc})`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (use_regex) {
|
|
129
|
+
message += ` using regex pattern`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (ignore_case) {
|
|
133
|
+
message += ` (case-insensitive)`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return FileUtils.createResponse(message);
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new Error(`Failed to search and replace: ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Count occurrences of a substring in a string
|
|
145
|
+
* @param {string} text - Main string
|
|
146
|
+
* @param {string} searchText - Substring to search for
|
|
147
|
+
* @returns {number} Number of occurrences
|
|
148
|
+
*/
|
|
149
|
+
static countOccurrences(text, searchText) {
|
|
150
|
+
if (!searchText) return 0;
|
|
151
|
+
return text.split(searchText).length - 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Count occurrences of a substring in a string (case-insensitive)
|
|
156
|
+
* @param {string} text - Main string
|
|
157
|
+
* @param {string} searchText - Substring to search for
|
|
158
|
+
* @returns {number} Number of occurrences
|
|
159
|
+
*/
|
|
160
|
+
static countOccurrencesCaseInsensitive(text, searchText) {
|
|
161
|
+
if (!searchText) return 0;
|
|
162
|
+
const textLower = text.toLowerCase();
|
|
163
|
+
const searchLower = searchText.toLowerCase();
|
|
164
|
+
return textLower.split(searchLower).length - 1;
|
|
165
|
+
} /**
|
|
166
|
+
* Case-insensitive string replacement
|
|
167
|
+
* @param {string} text - Main string
|
|
168
|
+
* @param {string} searchText - Substring to search for
|
|
169
|
+
* @param {string} replaceText - Replacement text
|
|
170
|
+
* @returns {string} Replaced string
|
|
171
|
+
*/
|
|
172
|
+
static replaceAllCaseInsensitive(text, searchText, replaceText) {
|
|
173
|
+
if (!searchText) return text;
|
|
174
|
+
|
|
175
|
+
const searchLower = searchText.toLowerCase();
|
|
176
|
+
const textLower = text.toLowerCase();
|
|
177
|
+
|
|
178
|
+
let result = '';
|
|
179
|
+
let lastIndex = 0;
|
|
180
|
+
|
|
181
|
+
let index = textLower.indexOf(searchLower);
|
|
182
|
+
while (index !== -1) {
|
|
183
|
+
// Add part before match
|
|
184
|
+
result += text.slice(lastIndex, index);
|
|
185
|
+
// Add replacement text
|
|
186
|
+
result += replaceText;
|
|
187
|
+
// Update position
|
|
188
|
+
lastIndex = index + searchText.length;
|
|
189
|
+
// Find next match
|
|
190
|
+
index = textLower.indexOf(searchLower, lastIndex);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add remaining part
|
|
194
|
+
result += text.slice(lastIndex);
|
|
195
|
+
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
}
|