fileditor-mcp 1.0.0 → 1.0.2
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/LICENSE +21 -21
- package/README.md +128 -128
- package/docs/MCP-INTERFACE.cn.md +1061 -975
- package/docs/MCP-INTERFACE.en.md +974 -881
- package/docs/README.cn.md +128 -128
- package/package.json +30 -26
- package/src/handlers/applyDiff.js +324 -298
- package/src/handlers/insertContent.js +153 -179
- package/src/handlers/listFiles.js +54 -65
- package/src/handlers/readFile.js +109 -109
- package/src/handlers/searchAndReplace.js +217 -198
- package/src/handlers/setWorkspace.js +26 -26
- package/src/handlers/writeFile.js +91 -97
- package/src/index.js +22 -20
- package/src/server.js +85 -85
- package/src/tools/toolDefinitions.js +301 -291
- package/src/utils/fileUtils.js +716 -238
- package/test/ApplyDiffHandler.test.js +754 -754
- package/test/InsertContentHandler.test.js +369 -371
- package/test/ListFilesHandler.test.js +300 -302
- 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,198 +1,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');
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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 line range
|
|
35
|
+
FileUtils.validateLineRange({
|
|
36
|
+
start_line,
|
|
37
|
+
end_line,
|
|
38
|
+
totalLines: lines.length
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Determine search range
|
|
42
|
+
const startIdx = start_line !== null ? Math.max(0, start_line - 1) : 0;
|
|
43
|
+
const endIdx = end_line !== null ? Math.min(lines.length, end_line) : lines.length;
|
|
44
|
+
|
|
45
|
+
// Validate search string
|
|
46
|
+
if (!search) {
|
|
47
|
+
// Empty search string, do not perform any replacement
|
|
48
|
+
const message = SearchAndReplaceHandler._buildResultMessage(path, 0, start_line, end_line, use_regex, ignore_case);
|
|
49
|
+
return FileUtils.createResponse(message);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Perform search and replace
|
|
53
|
+
const { processedLines, replacementCount } = SearchAndReplaceHandler._performSearchAndReplace(
|
|
54
|
+
lines.slice(startIdx, endIdx),
|
|
55
|
+
search,
|
|
56
|
+
replace,
|
|
57
|
+
use_regex,
|
|
58
|
+
ignore_case
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Rebuild file content
|
|
62
|
+
const newContent = [
|
|
63
|
+
...lines.slice(0, startIdx),
|
|
64
|
+
...processedLines,
|
|
65
|
+
...lines.slice(endIdx)
|
|
66
|
+
].join('\n');
|
|
67
|
+
|
|
68
|
+
// Write back to file
|
|
69
|
+
await FileUtils.writeFile(path, newContent);
|
|
70
|
+
|
|
71
|
+
// Build response message
|
|
72
|
+
const message = SearchAndReplaceHandler._buildResultMessage(path, replacementCount, start_line, end_line, use_regex, ignore_case);
|
|
73
|
+
return FileUtils.createResponse(message);
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Failed to search and replace: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Perform search and replace operation
|
|
82
|
+
* @param {string[]} lines - Lines to process
|
|
83
|
+
* @param {string} search - Search pattern
|
|
84
|
+
* @param {string} replace - Replacement text
|
|
85
|
+
* @param {boolean} use_regex - Whether to use regex
|
|
86
|
+
* @param {boolean} ignore_case - Whether to ignore case
|
|
87
|
+
* @returns {Object} Processing result
|
|
88
|
+
*/
|
|
89
|
+
static _performSearchAndReplace(lines, search, replace, use_regex, ignore_case) {
|
|
90
|
+
let replacementCount = 0;
|
|
91
|
+
|
|
92
|
+
const processedLines = lines.map(line => {
|
|
93
|
+
let newLine;
|
|
94
|
+
|
|
95
|
+
if (use_regex) {
|
|
96
|
+
const { processedLine, count } = SearchAndReplaceHandler._regexReplace(line, search, replace, ignore_case);
|
|
97
|
+
newLine = processedLine;
|
|
98
|
+
replacementCount += count;
|
|
99
|
+
} else {
|
|
100
|
+
const { processedLine, count } = SearchAndReplaceHandler._stringReplace(line, search, replace, ignore_case);
|
|
101
|
+
newLine = processedLine;
|
|
102
|
+
replacementCount += count;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return newLine;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return { processedLines, replacementCount };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Perform regex-based replacement
|
|
113
|
+
* @param {string} line - Line to process
|
|
114
|
+
* @param {string} search - Regex pattern
|
|
115
|
+
* @param {string} replace - Replacement text
|
|
116
|
+
* @param {boolean} ignore_case - Whether to ignore case
|
|
117
|
+
* @returns {Object} Result with processed line and count
|
|
118
|
+
*/
|
|
119
|
+
static _regexReplace(line, search, replace, ignore_case) {
|
|
120
|
+
try {
|
|
121
|
+
// Build regex flags
|
|
122
|
+
let flags = 'g';
|
|
123
|
+
if (ignore_case) {
|
|
124
|
+
flags += 'i';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const regex = new RegExp(search, flags);
|
|
128
|
+
const matches = line.match(regex);
|
|
129
|
+
const count = matches ? matches.length : 0;
|
|
130
|
+
const processedLine = line.replace(regex, replace);
|
|
131
|
+
|
|
132
|
+
return { processedLine, count };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new Error(`Invalid regular expression: ${search}. ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Perform string-based replacement
|
|
140
|
+
* @param {string} line - Line to process
|
|
141
|
+
* @param {string} search - Search string
|
|
142
|
+
* @param {string} replace - Replacement text
|
|
143
|
+
* @param {boolean} ignore_case - Whether to ignore case
|
|
144
|
+
* @returns {Object} Result with processed line and count
|
|
145
|
+
*/
|
|
146
|
+
static _stringReplace(line, search, replace, ignore_case) {
|
|
147
|
+
if (ignore_case) {
|
|
148
|
+
// Use regex for case-insensitive string replacement
|
|
149
|
+
const escapedSearch = SearchAndReplaceHandler._escapeRegex(search);
|
|
150
|
+
const regex = new RegExp(escapedSearch, 'gi');
|
|
151
|
+
const matches = line.match(regex);
|
|
152
|
+
const count = matches ? matches.length : 0;
|
|
153
|
+
const processedLine = line.replace(regex, replace);
|
|
154
|
+
|
|
155
|
+
return { processedLine, count };
|
|
156
|
+
} else {
|
|
157
|
+
// Case-sensitive string replacement
|
|
158
|
+
const count = SearchAndReplaceHandler._countOccurrences(line, search);
|
|
159
|
+
const processedLine = line.split(search).join(replace);
|
|
160
|
+
|
|
161
|
+
return { processedLine, count };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Escape special regex characters in a string
|
|
167
|
+
* @param {string} string - String to escape
|
|
168
|
+
* @returns {string} Escaped string
|
|
169
|
+
*/
|
|
170
|
+
static _escapeRegex(string) {
|
|
171
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Count occurrences of a substring in a string
|
|
176
|
+
* @param {string} text - Main string
|
|
177
|
+
* @param {string} searchText - Substring to search for
|
|
178
|
+
* @returns {number} Number of occurrences
|
|
179
|
+
*/
|
|
180
|
+
static _countOccurrences(text, searchText) {
|
|
181
|
+
if (!searchText) return 0;
|
|
182
|
+
return text.split(searchText).length - 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build result message
|
|
187
|
+
* @param {string} path - File path
|
|
188
|
+
* @param {number} replacementCount - Number of replacements
|
|
189
|
+
* @param {number|null} start_line - Start line
|
|
190
|
+
* @param {number|null} end_line - End line
|
|
191
|
+
* @param {boolean} use_regex - Whether regex was used
|
|
192
|
+
* @param {boolean} ignore_case - Whether case was ignored
|
|
193
|
+
* @returns {string} Result message
|
|
194
|
+
*/
|
|
195
|
+
static _buildResultMessage(path, replacementCount, start_line, end_line, use_regex, ignore_case) {
|
|
196
|
+
let message = `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
|
|
197
|
+
|
|
198
|
+
if (start_line || end_line) {
|
|
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
|
+
}
|