execute method

  1. @override
Future<ToolResult> execute(
  1. Map<String, dynamic> input
)
override

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');
  }
}