From d483ca372d7d3cc50a519aa2b88a2a18a5e5e168 Mon Sep 17 00:00:00 2001
From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com>
Date: Tue, 28 Oct 2025 16:39:49 +0100
Subject: [PATCH] revert: Remove edit file tool (#245)
---
CMakeLists.txt | 2 +-
ChatView/CMakeLists.txt | 3 +-
ChatView/ClientInterface.cpp | 2 -
ChatView/FileEditItem.cpp | 380 ---------------------------
ChatView/FileEditItem.hpp | 125 ---------
ChatView/qml/FileEditChangesItem.qml | 290 --------------------
tools/EditFileTool.cpp | 262 ------------------
tools/EditFileTool.hpp | 46 ----
tools/ToolsFactory.cpp | 2 -
9 files changed, 2 insertions(+), 1110 deletions(-)
delete mode 100644 ChatView/FileEditItem.cpp
delete mode 100644 ChatView/FileEditItem.hpp
delete mode 100644 ChatView/qml/FileEditChangesItem.qml
delete mode 100644 tools/EditFileTool.cpp
delete mode 100644 tools/EditFileTool.hpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5de9ddd..dd7bfe4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -122,7 +122,7 @@ add_qtc_plugin(QodeAssist
tools/ToolsManager.hpp tools/ToolsManager.cpp
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
- tools/EditFileTool.hpp tools/EditFileTool.cpp
+
tools/FindSymbolTool.hpp tools/FindSymbolTool.cpp
tools/FindFileTool.hpp tools/FindFileTool.cpp
tools/CreateNewFileTool.hpp tools/CreateNewFileTool.cpp
diff --git a/ChatView/CMakeLists.txt b/ChatView/CMakeLists.txt
index 63107a7..2135402 100644
--- a/ChatView/CMakeLists.txt
+++ b/ChatView/CMakeLists.txt
@@ -18,7 +18,6 @@ qt_add_qml_module(QodeAssistChatView
qml/parts/AttachedFilesPlace.qml
qml/parts/ErrorToast.qml
qml/ToolStatusItem.qml
- qml/FileEditChangesItem.qml
qml/parts/RulesViewer.qml
RESOURCES
@@ -48,7 +47,7 @@ qt_add_qml_module(QodeAssistChatView
ChatSerializer.hpp ChatSerializer.cpp
ChatView.hpp ChatView.cpp
ChatData.hpp
- FileEditItem.hpp FileEditItem.cpp
+
)
target_link_libraries(QodeAssistChatView
diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp
index 6bec293..95eafee 100644
--- a/ChatView/ClientInterface.cpp
+++ b/ChatView/ClientInterface.cpp
@@ -92,8 +92,6 @@ void ClientInterface::sendMessage(
"**Workflow patterns:**\n"
"- Code structure: find_cpp_symbol → read_files_by_path\n"
"- Find usages: find_cpp_symbol → search_in_project\n"
- "- Fix errors: get_issues_list → find_cpp_symbol → read_files_by_path → edit_file\n"
- "- Refactoring: find_cpp_symbol → read_files → search_in_project → edit_file\n\n"
"**Best practices:**\n"
"- Prefer find_cpp_symbol over search_in_project for code symbols\n"
"- Read once, edit comprehensively (atomic edits)\n"
diff --git a/ChatView/FileEditItem.cpp b/ChatView/FileEditItem.cpp
deleted file mode 100644
index 647534f..0000000
--- a/ChatView/FileEditItem.cpp
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2025 Petr Mironychev
- *
- * This file is part of QodeAssist.
- *
- * QodeAssist is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * QodeAssist is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with QodeAssist. If not, see .
- */
-
-#include "FileEditItem.hpp"
-
-#include "Logger.hpp"
-#include "settings/GeneralSettings.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace QodeAssist::Chat {
-
-QMutex FileEditItem::s_fileLockMutex;
-QSet FileEditItem::s_lockedFiles;
-
-FileEditItem::FileEditItem(QQuickItem *parent)
- : QQuickItem(parent)
-{}
-
-void FileEditItem::parseFromContent(const QString &content)
-{
- static const QLatin1String marker(EDIT_MARKER);
- int markerPos = content.indexOf(marker);
-
- if (markerPos == -1) {
- return;
- }
-
- int jsonStart = markerPos + marker.size();
- QString jsonStr = content.mid(jsonStart);
-
- QJsonParseError parseError;
- QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8(), &parseError);
-
- if (parseError.error != QJsonParseError::NoError) {
- return;
- }
-
- if (!doc.isObject()) {
- return;
- }
-
- QJsonObject editData = doc.object();
-
- m_editId = QString("edit_%1").arg(QDateTime::currentMSecsSinceEpoch());
- m_mode = editData["mode"].toString();
- m_filePath = editData["filepath"].toString();
- m_newText = editData["new_text"].toString();
- m_searchText = editData["search_text"].toString();
- m_lineBefore = editData["line_before"].toString();
- m_lineAfter = editData["line_after"].toString();
-
- if (m_mode.isEmpty()) {
- m_mode = m_searchText.isEmpty() ? "insert_after" : "replace";
- }
-
- m_addedLines = m_newText.split('\n').size();
- m_removedLines = m_searchText.isEmpty() ? 0 : m_searchText.split('\n').size();
-
- emit editIdChanged();
- emit modeChanged();
- emit filePathChanged();
- emit searchTextChanged();
- emit newTextChanged();
- emit lineBeforeChanged();
- emit lineAfterChanged();
- emit addedLinesChanged();
- emit removedLinesChanged();
-
- bool autoApplyEnabled = Settings::generalSettings().autoApplyFileEdits.value();
- if (autoApplyEnabled) {
- applyEditInternal(true);
- }
-}
-
-void FileEditItem::applyEdit()
-{
- applyEditInternal(false, 0);
-}
-
-void FileEditItem::applyEditInternal(bool isAutomatic, int retryCount)
-{
- if (isAutomatic) {
- if (m_status != EditStatus::Pending) {
- return;
- }
- } else {
- if (m_status != EditStatus::Pending && m_status != EditStatus::Reverted
- && m_status != EditStatus::Rejected) {
- return;
- }
- }
-
- if (!acquireFileLock(m_filePath)) {
- if (retryCount >= MAX_RETRY_COUNT) {
- rejectWithError(QString("File %1 is locked, exceeded retry limit").arg(m_filePath));
- return;
- }
-
- const int retryDelay = isAutomatic ? AUTO_APPLY_RETRY_DELAY_MS : RETRY_DELAY_MS;
- QTimer::singleShot(retryDelay, this, [this, isAutomatic, retryCount]() {
- applyEditInternal(isAutomatic, retryCount + 1);
- });
- return;
- }
-
- performApply();
- releaseFileLock(m_filePath);
-}
-
-void FileEditItem::revertEdit()
-{
- if (m_status != EditStatus::Applied) {
- return;
- }
-
- if (!acquireFileLock(m_filePath)) {
- QTimer::singleShot(RETRY_DELAY_MS, this, &FileEditItem::revertEdit);
- return;
- }
-
- performRevert();
- releaseFileLock(m_filePath);
-}
-
-void FileEditItem::performApply()
-{
- QString currentContent = readFile(m_filePath);
- m_originalContent = currentContent;
-
- QString editedContent;
-
- if (m_mode == "insert_after") {
- if (m_lineBefore.isEmpty()) {
- editedContent = m_newText + currentContent;
- } else {
- QList positions;
- int pos = 0;
- while ((pos = currentContent.indexOf(m_lineBefore, pos)) != -1) {
- positions.append(pos);
- pos += m_lineBefore.length();
- }
-
- if (positions.isEmpty()) {
- rejectWithError("Failed to apply edit: line_before not found");
- return;
- }
-
- int matchedPosition = -1;
-
- if (!m_lineAfter.isEmpty()) {
- for (int beforePos : positions) {
- int afterPos = beforePos + m_lineBefore.length();
- if (afterPos + m_lineAfter.length() <= currentContent.length()) {
- QString actualAfter = currentContent.mid(afterPos, m_lineAfter.length());
- if (actualAfter == m_lineAfter) {
- matchedPosition = afterPos;
- break;
- }
- }
- }
-
- if (matchedPosition == -1) {
- rejectWithError("Failed to apply edit: line_before found but line_after context doesn't match");
- return;
- }
- } else {
- matchedPosition = positions.first() + m_lineBefore.length();
- }
-
- editedContent = currentContent;
- editedContent.insert(matchedPosition, m_newText);
- }
-
- } else if (m_mode == "replace") {
- if (m_searchText.isEmpty()) {
- rejectWithError("REPLACE mode requires search_text to be specified");
- return;
- }
-
- bool hasLineBefore = !m_lineBefore.isEmpty();
- bool hasLineAfter = !m_lineAfter.isEmpty();
-
- QList searchPositions;
- int pos = 0;
- while ((pos = currentContent.indexOf(m_searchText, pos)) != -1) {
- searchPositions.append(pos);
- pos += m_searchText.length();
- }
-
- if (searchPositions.isEmpty()) {
- rejectWithError("Failed to apply edit: search_text not found. File may have been modified.");
- return;
- }
-
- int matchedPosition = -1;
- const int MAX_CONTEXT_DISTANCE = 500;
-
- for (int searchPos : searchPositions) {
- bool beforeMatches = true;
- bool afterMatches = true;
-
- if (hasLineBefore) {
- int searchStart = qMax(0, searchPos - MAX_CONTEXT_DISTANCE);
- int beforePos = currentContent.lastIndexOf(m_lineBefore, searchPos - 1);
-
- if (beforePos >= searchStart && beforePos < searchPos) {
- beforeMatches = true;
- } else {
- beforeMatches = false;
- }
- }
-
- if (hasLineAfter) {
- int afterPos = searchPos + m_searchText.length();
- int searchEnd = qMin(currentContent.length(), afterPos + MAX_CONTEXT_DISTANCE);
- int foundAfterPos = currentContent.indexOf(m_lineAfter, afterPos);
-
- if (foundAfterPos >= afterPos && foundAfterPos < searchEnd) {
- afterMatches = true;
- } else {
- afterMatches = false;
- }
- }
-
- bool isMatch = false;
-
- if (hasLineBefore && hasLineAfter) {
- isMatch = beforeMatches && afterMatches;
- } else if (hasLineBefore && !hasLineAfter) {
- isMatch = beforeMatches;
- } else if (!hasLineBefore && hasLineAfter) {
- isMatch = afterMatches;
- } else {
- isMatch = true;
- }
-
- if (isMatch) {
- matchedPosition = searchPos;
- break;
- }
- }
-
- if (matchedPosition == -1) {
- rejectWithError("Failed to apply edit: search_text found but context verification failed.");
- return;
- }
-
- editedContent = currentContent;
- editedContent.replace(matchedPosition, m_searchText.length(), m_newText);
-
- } else {
- rejectWithError(QString("Unknown edit mode: %1").arg(m_mode));
- return;
- }
-
- if (!writeFile(m_filePath, editedContent)) {
- rejectWithError(QString("Failed to write file: %1").arg(m_filePath));
- return;
- }
-
- finishWithSuccess(EditStatus::Applied, QString("Successfully applied edit to: %1").arg(m_filePath));
-}
-
-void FileEditItem::performRevert()
-{
- if (!writeFile(m_filePath, m_originalContent)) {
- rejectWithError(QString("Failed to write reverted file: %1").arg(m_filePath));
- return;
- }
-
- finishWithSuccess(EditStatus::Reverted, QString("Successfully reverted edit to: %1").arg(m_filePath));
-}
-
-void FileEditItem::rejectWithError(const QString &errorMessage)
-{
- setStatus(EditStatus::Rejected);
- setStatusMessage(errorMessage);
-}
-
-void FileEditItem::finishWithSuccess(EditStatus status, const QString &message)
-{
- setStatus(status);
- setStatusMessage(message);
-}
-
-void FileEditItem::setStatus(EditStatus status)
-{
- if (m_status == status)
- return;
-
- m_status = status;
- emit statusChanged();
-}
-
-void FileEditItem::setStatusMessage(const QString &message)
-{
- if (m_statusMessage == message)
- return;
-
- m_statusMessage = message;
- emit statusMessageChanged();
-}
-
-bool FileEditItem::writeFile(const QString &filePath, const QString &content)
-{
- QFile file(filePath);
- if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
- return false;
- }
-
- QTextStream stream(&file);
- stream.setAutoDetectUnicode(true);
- stream << content;
- file.close();
-
- if (stream.status() != QTextStream::Ok) {
- return false;
- }
-
- return true;
-}
-
-QString FileEditItem::readFile(const QString &filePath)
-{
- QFile file(filePath);
- if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
- return QString();
- }
-
- QTextStream stream(&file);
- stream.setAutoDetectUnicode(true);
- QString content = stream.readAll();
- file.close();
-
- return content;
-}
-
-bool FileEditItem::acquireFileLock(const QString &filePath)
-{
- QMutexLocker locker(&s_fileLockMutex);
-
- if (s_lockedFiles.contains(filePath)) {
- return false;
- }
-
- s_lockedFiles.insert(filePath);
- return true;
-}
-
-void FileEditItem::releaseFileLock(const QString &filePath)
-{
- QMutexLocker locker(&s_fileLockMutex);
- s_lockedFiles.remove(filePath);
-}
-
-} // namespace QodeAssist::Chat
diff --git a/ChatView/FileEditItem.hpp b/ChatView/FileEditItem.hpp
deleted file mode 100644
index 6a0fb0e..0000000
--- a/ChatView/FileEditItem.hpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2025 Petr Mironychev
- *
- * This file is part of QodeAssist.
- *
- * QodeAssist is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * QodeAssist is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with QodeAssist. If not, see .
- */
-
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-namespace QodeAssist::Chat {
-
-class FileEditItem : public QQuickItem
-{
- Q_OBJECT
- QML_ELEMENT
-
-public:
- enum class EditStatus {
- Pending,
- Applied,
- Rejected,
- Reverted
- };
- Q_ENUM(EditStatus)
-
- static constexpr const char *EDIT_MARKER = "QODEASSIST_FILE_EDIT:";
- static constexpr int RETRY_DELAY_MS = 100;
- static constexpr int AUTO_APPLY_RETRY_DELAY_MS = 50;
- static constexpr int MAX_RETRY_COUNT = 10;
-
- Q_PROPERTY(QString editId READ editId NOTIFY editIdChanged FINAL)
- Q_PROPERTY(QString mode READ mode NOTIFY modeChanged FINAL)
- Q_PROPERTY(QString filePath READ filePath NOTIFY filePathChanged FINAL)
- Q_PROPERTY(QString searchText READ searchText NOTIFY searchTextChanged FINAL)
- Q_PROPERTY(QString newText READ newText NOTIFY newTextChanged FINAL)
- Q_PROPERTY(QString lineBefore READ lineBefore NOTIFY lineBeforeChanged FINAL)
- Q_PROPERTY(QString lineAfter READ lineAfter NOTIFY lineAfterChanged FINAL)
- Q_PROPERTY(int addedLines READ addedLines NOTIFY addedLinesChanged FINAL)
- Q_PROPERTY(int removedLines READ removedLines NOTIFY removedLinesChanged FINAL)
- Q_PROPERTY(EditStatus status READ status NOTIFY statusChanged FINAL)
- Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged FINAL)
-
-public:
- explicit FileEditItem(QQuickItem *parent = nullptr);
-
- QString editId() const { return m_editId; }
- QString mode() const { return m_mode; }
- QString filePath() const { return m_filePath; }
- QString searchText() const { return m_searchText; }
- QString newText() const { return m_newText; }
- QString lineBefore() const { return m_lineBefore; }
- QString lineAfter() const { return m_lineAfter; }
- int addedLines() const { return m_addedLines; }
- int removedLines() const { return m_removedLines; }
- EditStatus status() const { return m_status; }
- QString statusMessage() const { return m_statusMessage; }
-
- Q_INVOKABLE void parseFromContent(const QString &content);
- Q_INVOKABLE void applyEdit();
- Q_INVOKABLE void revertEdit();
-
-signals:
- void editIdChanged();
- void modeChanged();
- void filePathChanged();
- void searchTextChanged();
- void newTextChanged();
- void lineBeforeChanged();
- void lineAfterChanged();
- void addedLinesChanged();
- void removedLinesChanged();
- void statusChanged();
- void statusMessageChanged();
-
-private:
- void setStatus(EditStatus status);
- void setStatusMessage(const QString &message);
- void applyEditInternal(bool isAutomatic, int retryCount = 0);
- void performApply();
- void performRevert();
- void rejectWithError(const QString &errorMessage);
- void finishWithSuccess(EditStatus status, const QString &message);
-
- bool writeFile(const QString &filePath, const QString &content);
- QString readFile(const QString &filePath);
-
- static bool acquireFileLock(const QString &filePath);
- static void releaseFileLock(const QString &filePath);
- static QMutex s_fileLockMutex;
- static QSet s_lockedFiles;
-
- QString m_editId;
- QString m_mode;
- QString m_filePath;
- QString m_searchText;
- QString m_newText;
- QString m_lineBefore;
- QString m_lineAfter;
- QString m_originalContent; // Stored when applying edit
- int m_addedLines = 0;
- int m_removedLines = 0;
- EditStatus m_status = EditStatus::Pending;
- QString m_statusMessage;
-};
-
-} // namespace QodeAssist::Chat
-
diff --git a/ChatView/qml/FileEditChangesItem.qml b/ChatView/qml/FileEditChangesItem.qml
deleted file mode 100644
index fab0a5e..0000000
--- a/ChatView/qml/FileEditChangesItem.qml
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2025 Petr Mironychev
- *
- * This file is part of QodeAssist.
- *
- * QodeAssist is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * QodeAssist is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with QodeAssist. If not, see .
- */
-
-import QtQuick
-import QtQuick.Layouts
-import ChatView
-import UIControls
-import "./parts"
-
-FileEditItem {
- id: root
-
- implicitHeight: fileEditView.implicitHeight
-
- Component.onCompleted: {
- root.parseFromContent(model.content)
- }
-
- readonly property int borderRadius: 4
- readonly property int contentMargin: 10
- readonly property int contentBottomPadding: 20
- readonly property int headerPadding: 8
- readonly property int statusIndicatorWidth: 4
-
- readonly property bool isPending: status === FileEditItem.Pending
- readonly property bool isApplied: status === FileEditItem.Applied
- readonly property bool isReverted: status === FileEditItem.Reverted
- readonly property bool isRejected: status === FileEditItem.Rejected
-
- readonly property color appliedColor: Qt.rgba(0.2, 0.8, 0.2, 0.8)
- readonly property color revertedColor: Qt.rgba(0.8, 0.6, 0.2, 0.8)
- readonly property color rejectedColor: palette.mid
- readonly property color pendingColor: palette.highlight
-
- readonly property color appliedBgColor: Qt.rgba(0.2, 0.8, 0.2, 0.3)
- readonly property color revertedBgColor: Qt.rgba(0.8, 0.6, 0.2, 0.3)
- readonly property color rejectedBgColor: Qt.rgba(0.8, 0.2, 0.2, 0.3)
-
- readonly property string codeFontFamily: {
- switch (Qt.platform.os) {
- case "windows": return "Consolas"
- case "osx": return "Menlo"
- case "linux": return "DejaVu Sans Mono"
- default: return "monospace"
- }
- }
- readonly property int codeFontSize: Qt.application.font.pointSize
-
- readonly property color statusColor: {
- if (isApplied) return appliedColor
- if (isReverted) return revertedColor
- if (isRejected) return rejectedColor
- return pendingColor
- }
-
- readonly property color statusBgColor: {
- if (isApplied) return appliedBgColor
- if (isReverted) return revertedBgColor
- if (isRejected) return rejectedBgColor
- return palette.button
- }
-
- readonly property string statusText: {
- if (isApplied) return qsTr("APPLIED")
- if (isReverted) return qsTr("REVERTED")
- if (isRejected) return qsTr("REJECTED")
- return ""
- }
-
- Rectangle {
- id: fileEditView
-
- property bool expanded: false
-
- anchors.fill: parent
- implicitHeight: expanded ? headerArea.height + contentColumn.height + root.contentBottomPadding
- : headerArea.height
- radius: root.borderRadius
-
- color: palette.base
-
- border.width: 1
- border.color: root.isPending
- ? (color.hslLightness > 0.5 ? Qt.darker(color, 1.3) : Qt.lighter(color, 1.3))
- : Qt.alpha(root.statusColor, 0.6)
-
- clip: true
-
- states: [
- State {
- name: "expanded"
- when: fileEditView.expanded
- PropertyChanges { target: contentColumn; opacity: 1 }
- },
- State {
- name: "collapsed"
- when: !fileEditView.expanded
- PropertyChanges { target: contentColumn; opacity: 0 }
- }
- ]
-
- transitions: Transition {
- NumberAnimation {
- properties: "implicitHeight,opacity"
- duration: 200
- easing.type: Easing.InOutQuad
- }
- }
-
- MouseArea {
- id: headerArea
- width: parent.width
- height: headerRow.height + 16
- cursorShape: Qt.PointingHandCursor
- onClicked: fileEditView.expanded = !fileEditView.expanded
-
- RowLayout {
- id: headerRow
-
- anchors {
- verticalCenter: parent.verticalCenter
- left: parent.left
- right: actionButtons.left
- leftMargin: root.contentMargin
- rightMargin: root.contentMargin
- }
- spacing: root.headerPadding
-
- Rectangle {
- width: root.statusIndicatorWidth
- height: headerText.height
- radius: 2
- color: root.statusColor
- }
-
- Text {
- id: headerText
- Layout.fillWidth: true
- text: {
- var modeText = root.searchText.length > 0 ? qsTr("Replace") : qsTr("Append")
- return qsTr("%1: %2 (+%3 -%4)")
- .arg(modeText)
- .arg(root.filePath)
- .arg(root.addedLines)
- .arg(root.removedLines)
- }
- font.pixelSize: 12
- font.bold: true
- color: palette.text
- elide: Text.ElideMiddle
- }
-
- Text {
- text: fileEditView.expanded ? "▼" : "▶"
- font.pixelSize: 10
- color: palette.mid
- }
-
- Badge {
- visible: !root.isPending
- text: root.statusText
- color: root.statusBgColor
- }
- }
-
- Row {
- id: actionButtons
-
- anchors {
- right: parent.right
- rightMargin: 5
- verticalCenter: parent.verticalCenter
- }
- spacing: 6
-
- QoAButton {
- text: qsTr("Apply")
- enabled: root.isPending || root.isReverted || root.isRejected
- visible: !root.isApplied
- onClicked: root.applyEdit()
- }
-
- QoAButton {
- text: qsTr("Revert")
- enabled: root.isApplied
- visible: !root.isReverted && !root.isRejected
- onClicked: root.revertEdit()
- }
- }
- }
-
- ColumnLayout {
- id: contentColumn
-
- anchors {
- left: parent.left
- right: parent.right
- top: headerArea.bottom
- margins: root.contentMargin
- }
- spacing: 4
- visible: opacity > 0
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: oldContentText.implicitHeight + 8
- color: Qt.rgba(1, 0.2, 0.2, 0.1)
- radius: 4
- border.width: 1
- border.color: Qt.rgba(1, 0.2, 0.2, 0.3)
- visible: root.searchText.length > 0
-
- TextEdit {
- id: oldContentText
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- margins: 4
- }
- text: root.searchText
- font.family: root.codeFontFamily
- font.pixelSize: root.codeFontSize
- color: Qt.rgba(1, 0.2, 0.2, 0.9)
- wrapMode: TextEdit.Wrap
- readOnly: true
- selectByMouse: true
- selectByKeyboard: true
- textFormat: TextEdit.PlainText
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: newContentText.implicitHeight + 8
- color: Qt.rgba(0.2, 0.8, 0.2, 0.1)
- radius: 4
- border.width: 1
- border.color: Qt.rgba(0.2, 0.8, 0.2, 0.3)
-
- TextEdit {
- id: newContentText
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- margins: 4
- }
- text: root.newText
- font.family: root.codeFontFamily
- font.pixelSize: root.codeFontSize
- color: Qt.rgba(0.2, 0.8, 0.2, 0.9)
- wrapMode: TextEdit.Wrap
- readOnly: true
- selectByMouse: true
- selectByKeyboard: true
- textFormat: TextEdit.PlainText
- }
- }
-
- Text {
- Layout.fillWidth: true
- visible: root.statusMessage.length > 0
- text: root.statusMessage
- font.pixelSize: 11
- font.italic: true
- color: root.isApplied
- ? Qt.rgba(0.2, 0.6, 0.2, 1)
- : Qt.rgba(0.8, 0.2, 0.2, 1)
- wrapMode: Text.WordWrap
- }
- }
- }
-}
diff --git a/tools/EditFileTool.cpp b/tools/EditFileTool.cpp
deleted file mode 100644
index 13439a9..0000000
--- a/tools/EditFileTool.cpp
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2025 Petr Mironychev
- *
- * This file is part of QodeAssist.
- *
- * QodeAssist is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * QodeAssist is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with QodeAssist. If not, see .
- */
-
-#include "EditFileTool.hpp"
-#include "ToolExceptions.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace QodeAssist::Tools {
-
-EditFileTool::EditFileTool(QObject *parent)
- : BaseTool(parent)
- , m_ignoreManager(new Context::IgnoreManager(this))
-{}
-
-QString EditFileTool::name() const
-{
- return "edit_file";
-}
-
-QString EditFileTool::stringName() const
-{
- return {"Editing file"};
-}
-
-QString EditFileTool::description() const
-{
- return "Edit project files with two modes: REPLACE (find and replace text) or INSERT_AFTER "
- "(insert after specific line). All text parameters must be complete lines with trailing "
- "newlines (\\n auto-added if missing).\n"
- "\n"
- "REPLACE MODE:\n"
- "- Finds search_text and replaces with new_text\n"
- "- Context verification: line_before/line_after searched NEAR search_text (~500 chars), "
- "not necessarily adjacent\n"
- "- Both context lines: most precise matching\n"
- "- One context line: directional search (before/after)\n"
- "- No context: first occurrence\n"
- "\n"
- "INSERT_AFTER MODE:\n"
- "- Inserts new_text RIGHT AFTER line_before\n"
- "- Empty line_before: inserts at file start (useful for empty files)\n"
- "- line_after: must IMMEDIATELY follow line_before for verification\n"
- "- search_text is ignored\n"
- "\n"
- "BEST PRACTICES:\n"
- "- Sequential additions: use INSERT_AFTER with previous addition as line_before\n"
- "- Provide stable context lines that won't change\n"
- "- Make atomic edits (one comprehensive change vs multiple small ones)";
-}
-
-QJsonObject EditFileTool::getDefinition(LLMCore::ToolSchemaFormat format) const
-{
- QJsonObject properties;
-
- QJsonObject modeProperty;
- modeProperty["type"] = "string";
- modeProperty["enum"] = QJsonArray({"replace", "insert_after"});
- modeProperty["description"] = "Edit mode: 'replace' (replace search_text with new_text), "
- "'insert_after' (insert new_text after line_before)";
- properties["mode"] = modeProperty;
-
- QJsonObject filepathProperty;
- filepathProperty["type"] = "string";
- filepathProperty["description"] = "The absolute or relative file path to edit";
- properties["filepath"] = filepathProperty;
-
- QJsonObject newTextProperty;
- newTextProperty["type"] = "string";
- newTextProperty["description"] = "Complete line(s) to insert/replace/append. Trailing newline (\\n) auto-added if missing. "
- "Example: 'int main(int argc, char *argv[]) {\\n' or 'void foo();\\nvoid bar();\\n'";
- properties["new_text"] = newTextProperty;
-
- QJsonObject searchTextProperty;
- searchTextProperty["type"] = "string";
- searchTextProperty["description"]
- = "Complete line(s) to search for and replace. Trailing newline (\\n) auto-added if missing. "
- "REQUIRED for 'replace' mode, IGNORED for other modes. "
- "Example: 'int main() {\\n' or 'void foo();\\n'";
- properties["search_text"] = searchTextProperty;
-
- QJsonObject lineBeforeProperty;
- lineBeforeProperty["type"] = "string";
- lineBeforeProperty["description"] = "Complete line for context verification. Trailing newline (\\n) auto-added if missing. "
- "Usage depends on mode:\n"
- "- 'replace': OPTIONAL, searched BEFORE search_text (within ~500 chars, not necessarily adjacent)\n"
- "- 'insert_after': OPTIONAL, new_text inserted RIGHT AFTER this line. "
- "If empty, inserts at the beginning of the file (useful for empty files)\n"
- "Example: 'class Movie {\\n' or '#include \\n'";
- properties["line_before"] = lineBeforeProperty;
-
- QJsonObject lineAfterProperty;
- lineAfterProperty["type"] = "string";
- lineAfterProperty["description"] = "Complete line for context verification. Trailing newline (\\n) auto-added if missing. "
- "Usage depends on mode:\n"
- "- 'replace': OPTIONAL, searched AFTER search_text (within ~500 chars, not necessarily adjacent)\n"
- "- 'insert_after': OPTIONAL, must IMMEDIATELY follow line_before for verification\n"
- "Example: '}\\n' or 'public:\\n'";
- properties["line_after"] = lineAfterProperty;
-
- QJsonObject definition;
- definition["type"] = "object";
- definition["properties"] = properties;
-
- QJsonArray required;
- required.append("mode");
- required.append("filepath");
- required.append("new_text");
- definition["required"] = required;
-
- switch (format) {
- case LLMCore::ToolSchemaFormat::OpenAI:
- return customizeForOpenAI(definition);
- case LLMCore::ToolSchemaFormat::Claude:
- return customizeForClaude(definition);
- case LLMCore::ToolSchemaFormat::Ollama:
- return customizeForOllama(definition);
- case LLMCore::ToolSchemaFormat::Google:
- return customizeForGoogle(definition);
- }
-
- return definition;
-}
-
-LLMCore::ToolPermissions EditFileTool::requiredPermissions() const
-{
- return LLMCore::ToolPermissions(LLMCore::ToolPermission::FileSystemRead)
- | LLMCore::ToolPermissions(LLMCore::ToolPermission::FileSystemWrite);
-}
-
-QFuture EditFileTool::executeAsync(const QJsonObject &input)
-{
- return QtConcurrent::run([this, input]() -> QString {
- QString mode = input["mode"].toString();
- if (mode.isEmpty()) {
- throw ToolInvalidArgument("Error: mode parameter is required. Must be one of: 'replace', 'insert_after'");
- }
-
- if (mode != "replace" && mode != "insert_after") {
- throw ToolInvalidArgument(QString("Error: invalid mode '%1'. Must be one of: 'replace', 'insert_after'").arg(mode));
- }
-
- QString inputFilepath = input["filepath"].toString();
- if (inputFilepath.isEmpty()) {
- throw ToolInvalidArgument("Error: filepath parameter is required");
- }
-
- QString newText = input["new_text"].toString();
- if (newText.isEmpty()) {
- throw ToolInvalidArgument("Error: new_text parameter is required");
- }
-
- QString searchText = input["search_text"].toString();
- QString lineBefore = input["line_before"].toString();
- QString lineAfter = input["line_after"].toString();
-
- if (mode == "replace" && searchText.isEmpty()) {
- throw ToolInvalidArgument("Error: search_text is required for 'replace' mode");
- }
-
- // Normalize text fields: ensure trailing newline if not empty
- // This handles cases where LLM forgets to add \n
- auto normalizeText = [](QString &text) {
- if (!text.isEmpty() && !text.endsWith('\n')) {
- LOG_MESSAGE(QString("EditFileTool: normalizing text, adding trailing newline (length: %1)").arg(text.length()));
- text += '\n';
- }
- };
-
- normalizeText(newText);
- if (!searchText.isEmpty()) normalizeText(searchText);
- if (!lineBefore.isEmpty()) normalizeText(lineBefore);
- if (!lineAfter.isEmpty()) normalizeText(lineAfter);
-
- QString filePath;
- QFileInfo fileInfo(inputFilepath);
-
- if (fileInfo.isAbsolute()) {
- filePath = inputFilepath;
- } else {
- auto projects = ProjectExplorer::ProjectManager::projects();
- if (!projects.isEmpty() && projects.first()) {
- QString projectDir = projects.first()->projectDirectory().toUrlishString();
- filePath = QDir(projectDir).absoluteFilePath(inputFilepath);
- } else {
- filePath = QFileInfo(inputFilepath).absoluteFilePath();
- }
- }
-
- if (!QFileInfo::exists(filePath)) {
- throw ToolRuntimeError(QString("Error: File '%1' does not exist").arg(filePath));
- }
-
- bool isInProject = Context::ProjectUtils::isFileInProject(filePath);
-
- if (!isInProject) {
- const auto &settings = Settings::generalSettings();
- if (!settings.allowAccessOutsideProject()) {
- throw ToolRuntimeError(
- QString("Error: File '%1' is outside the project scope. "
- "Enable 'Allow file access outside project' in settings to edit files outside project scope.")
- .arg(filePath));
- }
- LOG_MESSAGE(QString("Editing file outside project scope: %1").arg(filePath));
- }
-
- auto project = isInProject ? ProjectExplorer::ProjectManager::projectForFile(
- Utils::FilePath::fromString(filePath)) : nullptr;
-
- if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
- throw ToolRuntimeError(
- QString("Error: File '%1' is excluded by .qodeassistignore").arg(inputFilepath));
- }
-
- QJsonObject result;
- result["type"] = "file_edit";
- result["mode"] = mode;
- result["filepath"] = filePath;
- result["new_text"] = newText;
- result["search_text"] = searchText;
- result["line_before"] = lineBefore;
- result["line_after"] = lineAfter;
-
- QJsonDocument doc(result);
- return QString("QODEASSIST_FILE_EDIT:%1")
- .arg(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
- });
-}
-
-} // namespace QodeAssist::Tools
-
diff --git a/tools/EditFileTool.hpp b/tools/EditFileTool.hpp
deleted file mode 100644
index f25c63d..0000000
--- a/tools/EditFileTool.hpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2025 Petr Mironychev
- *
- * This file is part of QodeAssist.
- *
- * QodeAssist is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * QodeAssist is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with QodeAssist. If not, see .
- */
-
-#pragma once
-
-#include
-#include
-
-namespace QodeAssist::Tools {
-
-class EditFileTool : public LLMCore::BaseTool
-{
- Q_OBJECT
-public:
- explicit EditFileTool(QObject *parent = nullptr);
-
- QString name() const override;
- QString stringName() const override;
- QString description() const override;
- QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
- LLMCore::ToolPermissions requiredPermissions() const override;
-
- QFuture executeAsync(const QJsonObject &input = QJsonObject()) override;
-
-private:
- Context::IgnoreManager *m_ignoreManager;
-};
-
-} // namespace QodeAssist::Tools
-
diff --git a/tools/ToolsFactory.cpp b/tools/ToolsFactory.cpp
index 90eb80f..613556b 100644
--- a/tools/ToolsFactory.cpp
+++ b/tools/ToolsFactory.cpp
@@ -25,7 +25,6 @@
#include
#include "CreateNewFileTool.hpp"
-#include "EditFileTool.hpp"
#include "FindFileTool.hpp"
#include "FindSymbolTool.hpp"
#include "GetIssuesListTool.hpp"
@@ -49,7 +48,6 @@ void ToolsFactory::registerTools()
registerTool(new ListProjectFilesTool(this));
registerTool(new SearchInProjectTool(this));
registerTool(new GetIssuesListTool(this));
- registerTool(new EditFileTool(this));
registerTool(new FindSymbolTool(this));
registerTool(new FindFileTool(this));
registerTool(new CreateNewFileTool(this));