feat: Add open in editor and remove from list (#267)

* refactor: Rename and move chat items
* feat: Add hotkeys for open in editor and remove file from list
* feat: Add opening by system
* feat: Add context action menu
This commit is contained in:
Petr Mironychev
2025-11-19 01:15:43 +01:00
committed by GitHub
parent bcdec96d92
commit ef73895823
16 changed files with 199 additions and 25 deletions

View File

@ -10,18 +10,20 @@ qt_add_qml_module(QodeAssistChatView
QtQuick QtQuick
QML_FILES QML_FILES
qml/RootItem.qml qml/RootItem.qml
qml/ChatItem.qml
qml/dialog/CodeBlock.qml qml/chatparts/CodeBlock.qml
qml/dialog/TextBlock.qml qml/chatparts/FileEditBlock.qml
qml/parts/TopBar.qml qml/chatparts/TextBlock.qml
qml/parts/BottomBar.qml qml/chatparts/ThinkingBlock.qml
qml/parts/AttachedFilesPlace.qml qml/chatparts/ToolBlock.qml
qml/parts/Toast.qml qml/chatparts/ChatItem.qml
qml/ToolStatusItem.qml
qml/ThinkingStatusItem.qml qml/controls/AttachedFilesPlace.qml
qml/FileEditItem.qml qml/controls/BottomBar.qml
qml/parts/RulesViewer.qml qml/controls/FileEditsActionBar.qml
qml/parts/FileEditsActionBar.qml qml/controls/RulesViewer.qml
qml/controls/Toast.qml
qml/controls/TopBar.qml
RESOURCES RESOURCES
icons/attach-file-light.svg icons/attach-file-light.svg
@ -56,7 +58,7 @@ qt_add_qml_module(QodeAssistChatView
ChatSerializer.hpp ChatSerializer.cpp ChatSerializer.hpp ChatSerializer.cpp
ChatView.hpp ChatView.cpp ChatView.hpp ChatView.cpp
ChatData.hpp ChatData.hpp
FileItem.hpp FileItem.cpp
) )
target_link_libraries(QodeAssistChatView target_link_libraries(QodeAssistChatView

76
ChatView/FileItem.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "FileItem.hpp"
#include <QDesktopServices>
#include <QUrl>
#include <coreplugin/editormanager/editormanager.h>
#include <logger/Logger.hpp>
#include <utils/filepath.h>
namespace QodeAssist::Chat {
FileItem::FileItem(QQuickItem *parent)
: QQuickItem(parent)
{}
void FileItem::openFileInEditor()
{
if (m_filePath.isEmpty()) {
return;
}
Utils::FilePath filePathObj = Utils::FilePath::fromString(m_filePath);
Core::IEditor *editor = Core::EditorManager::openEditor(filePathObj);
if (!editor) {
LOG_MESSAGE(QString("Failed to open file in editor: %1").arg(m_filePath));
}
}
void FileItem::openFileInExternalEditor()
{
if (m_filePath.isEmpty()) {
return;
}
bool success = QDesktopServices::openUrl(QUrl::fromLocalFile(m_filePath));
if (success) {
LOG_MESSAGE(QString("Opened file in external application: %1").arg(m_filePath));
} else {
LOG_MESSAGE(QString("Failed to open file externally: %1").arg(m_filePath));
}
}
QString FileItem::filePath() const
{
return m_filePath;
}
void FileItem::setFilePath(const QString &newFilePath)
{
if (m_filePath == newFilePath)
return;
m_filePath = newFilePath;
emit filePathChanged();
}
} // namespace QodeAssist::Chat

48
ChatView/FileItem.hpp Normal file
View File

@ -0,0 +1,48 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QQuickItem>
namespace QodeAssist::Chat {
class FileItem: public QQuickItem
{
Q_OBJECT
QML_NAMED_ELEMENT(FileItem)
Q_PROPERTY(QString filePath READ filePath WRITE setFilePath NOTIFY filePathChanged)
public:
FileItem(QQuickItem *parent = nullptr);
Q_INVOKABLE void openFileInEditor();
Q_INVOKABLE void openFileInExternalEditor();
QString filePath() const;
void setFilePath(const QString &newFilePath);
signals:
void filePathChanged();
private:
QString m_filePath;
};
}

View File

@ -24,7 +24,8 @@ import QtQuick.Layouts
import ChatView import ChatView
import UIControls import UIControls
import Qt.labs.platform as Platform import Qt.labs.platform as Platform
import "./parts"
import "./chatparts"
ChatRootView { ChatRootView {
id: root id: root
@ -188,7 +189,7 @@ ChatRootView {
Component { Component {
id: toolMessageComponent id: toolMessageComponent
ToolStatusItem { ToolBlock {
id: toolsItem id: toolsItem
width: parent.width width: parent.width
@ -208,7 +209,7 @@ ChatRootView {
Component { Component {
id: fileEditMessageComponent id: fileEditMessageComponent
FileEditItem { FileEditBlock {
width: parent.width width: parent.width
editContent: model.content editContent: model.content
@ -233,7 +234,7 @@ ChatRootView {
Component { Component {
id: thinkingMessageComponent id: thinkingMessageComponent
ThinkingStatusItem { ThinkingBlock {
id: thinking id: thinking
width: parent.width width: parent.width

View File

@ -23,8 +23,6 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import UIControls import UIControls
import "./dialog"
Rectangle { Rectangle {
id: root id: root

View File

@ -40,19 +40,68 @@ Flow {
Repeater { Repeater {
id: attachRepeater id: attachRepeater
delegate: Rectangle { delegate: FileItem {
id: fileItem
required property int index required property int index
required property string modelData required property string modelData
filePath: modelData
height: 30 height: 30
width: contentRow.width + 10 width: contentRow.width + 10
Rectangle {
anchors.fill: parent
radius: 4 radius: 4
color: palette.button color: palette.button
border.width: 1 border.width: 1
border.color: mouse.hovered ? palette.highlight : root.accentColor border.color: mouse.containsMouse ? palette.highlight : root.accentColor
}
HoverHandler { MouseArea {
id: mouse id: mouse
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
contextMenu.popup()
} else if (mouse.button === Qt.MiddleButton ||
(mouse.button === Qt.LeftButton && (mouse.modifiers & Qt.ControlModifier))) {
root.removeFileFromListByIndex(fileItem.index)
} else if (mouse.modifiers & Qt.ShiftModifier) {
fileItem.openFileInExternalEditor()
} else {
fileItem.openFileInEditor()
}
}
ToolTip.visible: containsMouse
ToolTip.delay: 500
ToolTip.text: "Click: Open in Qt Creator\nShift+Click: Open in external editor\nCtrl+Click / Middle Click: Remove"
}
Menu {
id: contextMenu
MenuItem {
text: "Open in Qt Creator"
onTriggered: fileItem.openFileInEditor()
}
MenuItem {
text: "Open in External Editor"
onTriggered: fileItem.openFileInExternalEditor()
}
MenuSeparator {}
MenuItem {
text: "Remove"
onTriggered: root.removeFileFromListByIndex(fileItem.index)
}
} }
Row { Row {