execute method
Execute the tool with the given input.
Implementation
@override
Future<ToolResult> execute(Map<String, dynamic> input) async {
final filePath = input['file_path'] as String?;
final oldString = input['old_string'] as String?;
final newString = input['new_string'] as String?;
final replaceAll = input['replace_all'] as bool? ?? false;
// Validate required parameters
if (filePath == null || filePath.isEmpty) {
return ToolResult.error('Missing required parameter: file_path');
}
if (!p.isAbsolute(filePath)) {
return ToolResult.error(
'file_path must be an absolute path, got: $filePath',
);
}
if (oldString == null) {
return ToolResult.error('Missing required parameter: old_string');
}
if (newString == null) {
return ToolResult.error('Missing required parameter: new_string');
}
if (oldString == newString) {
return ToolResult.error(
'No changes to make: old_string and new_string are exactly the same.',
);
}
final absoluteFilePath = _expandPath(filePath);
// SECURITY: Skip filesystem operations for UNC paths
if (absoluteFilePath.startsWith('\\\\') ||
absoluteFilePath.startsWith('//')) {
return ToolResult.error('UNC paths are not supported for editing');
}
// Check file size
try {
final stat = File(absoluteFilePath).statSync();
if (stat.size > maxEditFileSize) {
return ToolResult.error(
'File is too large to edit (${formatFileSize(stat.size)}). '
'Maximum editable file size is ${formatFileSize(maxEditFileSize)}.',
);
}
} on FileSystemException {
// File may not exist yet (creation via empty old_string)
}
// Read file
final meta = readFileForEdit(absoluteFilePath);
// File does not exist
if (!meta.fileExists) {
if (oldString.isEmpty) {
// Empty old_string on nonexistent file means new file creation
try {
final dir = Directory(p.dirname(absoluteFilePath));
if (!dir.existsSync()) dir.createSync(recursive: true);
writeTextContent(
absoluteFilePath,
newString,
'utf-8',
LineEndingType.lf,
);
return ToolResult.success(
'The file $filePath has been created successfully.',
);
} catch (e) {
return ToolResult.error('Error creating file: $e');
}
}
return ToolResult.error(
'File does not exist: $filePath. '
'Current working directory is ${Directory.current.path}.',
);
}
// File exists with empty old_string -- only valid if file is empty
if (oldString.isEmpty) {
if (meta.content.trim().isNotEmpty) {
return ToolResult.error(
'Cannot create new file - file already exists.',
);
}
// Empty file with empty old_string is valid
try {
writeTextContent(
absoluteFilePath,
newString,
meta.encoding,
meta.lineEndings,
);
return ToolResult.success(
'The file $filePath has been updated successfully.',
);
} catch (e) {
return ToolResult.error('Error writing file: $e');
}
}
// Reject .ipynb files
if (absoluteFilePath.endsWith('.ipynb')) {
return ToolResult.error(
'File is a Jupyter Notebook. Use the NotebookEdit tool to edit this file.',
);
}
try {
// Use findActualString for quote normalization
final actualOldString =
findActualString(meta.content, oldString) ?? oldString;
// Check if old_string exists
if (!meta.content.contains(actualOldString)) {
return _buildNotFoundError(filePath, oldString, meta.content);
}
// Check uniqueness (unless replaceAll)
final occurrences = actualOldString.allMatches(meta.content).length;
if (!replaceAll && occurrences > 1) {
return ToolResult.error(
'Found $occurrences matches of the string to replace, but '
'replace_all is false. To replace all occurrences, set '
'replace_all to true. To replace only one occurrence, please '
'provide more context to uniquely identify the instance.',
);
}
// Preserve curly quotes in new_string
final actualNewString = preserveQuoteStyle(
oldString,
actualOldString,
newString,
);
// Apply the edit
final updatedFile = replaceAll
? meta.content.replaceAll(actualOldString, actualNewString)
: meta.content.replaceFirst(actualOldString, actualNewString);
// Ensure parent directory exists
final dir = Directory(p.dirname(absoluteFilePath));
if (!dir.existsSync()) dir.createSync(recursive: true);
// Write to disk
writeTextContent(
absoluteFilePath,
updatedFile,
meta.encoding,
meta.lineEndings,
);
// Build diff
final diff = _buildDiff(meta.content, updatedFile, filePath);
// Count changes
final oldLineCount = '\n'.allMatches(meta.content).length + 1;
final newLineCount = '\n'.allMatches(updatedFile).length + 1;
final linesChanged = (newLineCount - oldLineCount).abs();
final output = FileEditOutput(
success: true,
message: 'The file $filePath has been updated successfully.',
filePath: filePath,
oldString: actualOldString,
newString: newString,
originalFile: meta.content,
replaceAll: replaceAll,
linesChanged: linesChanged,
occurrencesReplaced: replaceAll ? occurrences : 1,
diff: diff,
);
final resultBuf = StringBuffer();
resultBuf.write(output.message);
if (replaceAll) {
resultBuf.write(' All occurrences were successfully replaced.');
}
return ToolResult.success(
resultBuf.toString(),
metadata: output.toMetadata(),
);
} catch (e) {
return ToolResult.error('Error editing file: $e');
}
}