From a6038f4180ca2ed52f40c2e9660a774b769fbcf8 Mon Sep 17 00:00:00 2001 From: Lu Zhen Date: Wed, 1 Nov 2023 16:59:06 +0800 Subject: [PATCH] feat: add code coplilot with comment&generate feature Log: Change-Id: I2024bc8eaa6764f025b3a4dee1f20621b2aca349 --- .../scintilla/qt/ScintillaEditBase/PlatQt.cpp | 5 +- src/common/util/eventdefinitions.h | 2 + src/plugins/codeeditor/codeeditor.cpp | 34 +++- .../mainframe/naveditmainwindow.cpp | 1 + .../codeeditor/mainframe/texteditkeeper.cpp | 12 +- .../codeeditor/mainframe/texteditkeeper.h | 10 +- .../textedittabwidget/scintillaeditextern.cpp | 55 ++++++- .../textedittabwidget/scintillaeditextern.h | 9 ++ .../textedittabwidget/style/stylelsp.cpp | 3 + .../codeeditor/textedittabwidget/textedit.cpp | 6 + .../textedittabwidget/texteditsplitter.cpp | 64 ++++++++ .../textedittabwidget/texteditsplitter.h | 8 + .../textedittabwidget/textedittabbar.cpp | 3 + .../textedittabwidget/textedittabwidget.h | 1 + src/plugins/codegeex/CMakeLists.txt | 6 +- src/plugins/codegeex/askpage/askpage.cpp | 8 - src/plugins/codegeex/codegeex.cpp | 7 +- src/plugins/codegeex/codegeex/askapi.cpp | 1 - .../codegeex/{copilot.cpp => copilotapi.cpp} | 53 +++++-- .../codegeex/{copilot.h => copilotapi.h} | 18 ++- src/plugins/codegeex/copilot.cpp | 145 ++++++++++++++++++ src/plugins/codegeex/copilot.h | 51 ++++++ src/plugins/codegeex/eventreceiver.cpp | 21 ++- src/services/CMakeLists.txt | 1 + src/services/editor/editorservice.h | 45 ++++++ 25 files changed, 525 insertions(+), 44 deletions(-) rename src/plugins/codegeex/codegeex/{copilot.cpp => copilotapi.cpp} (62%) rename src/plugins/codegeex/codegeex/{copilot.h => copilotapi.h} (86%) create mode 100644 src/plugins/codegeex/copilot.cpp create mode 100644 src/plugins/codegeex/copilot.h create mode 100644 src/services/editor/editorservice.h diff --git a/3rdparty/unioncode-scintilla515/scintilla/qt/ScintillaEditBase/PlatQt.cpp b/3rdparty/unioncode-scintilla515/scintilla/qt/ScintillaEditBase/PlatQt.cpp index 7d73eb731..a64fd084a 100644 --- a/3rdparty/unioncode-scintilla515/scintilla/qt/ScintillaEditBase/PlatQt.cpp +++ b/3rdparty/unioncode-scintilla515/scintilla/qt/ScintillaEditBase/PlatQt.cpp @@ -794,7 +794,10 @@ QRect ScreenRectangleForPoint(QPoint posGlobal) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const QScreen *screen = QGuiApplication::screenAt(posGlobal); - return screen->availableGeometry(); + // added by mozart, to void crash when screen is null. + if (screen) + return screen->availableGeometry(); + return {}; #else const QDesktopWidget *desktop = QApplication::desktop(); return desktop->availableGeometry(posGlobal); diff --git a/src/common/util/eventdefinitions.h b/src/common/util/eventdefinitions.h index 4f49bec5d..0f035d5eb 100644 --- a/src/common/util/eventdefinitions.h +++ b/src/common/util/eventdefinitions.h @@ -60,6 +60,8 @@ OPI_OBJECT(editor, OPI_INTERFACE(replaceText, "text", "target", "repalceType") OPI_INTERFACE(switchContext, "name") OPI_INTERFACE(switchWorkspace, "name") + OPI_INTERFACE(contextMenu, "menu") + OPI_INTERFACE(keyPressEvent, "event") ) OPI_OBJECT(symbol, diff --git a/src/plugins/codeeditor/codeeditor.cpp b/src/plugins/codeeditor/codeeditor.cpp index 4d0beffc9..21e40b847 100644 --- a/src/plugins/codeeditor/codeeditor.cpp +++ b/src/plugins/codeeditor/codeeditor.cpp @@ -22,6 +22,7 @@ #include "services/window/windowservice.h" #include "services/language/languageservice.h" +#include "services/editor/editorservice.h" #include "common/widget/outputpane.h" #include @@ -57,13 +58,13 @@ bool CodeEditor::start() auto &ctx = dpfInstance.serviceContext(); WindowService *windowService = ctx.service(WindowService::name()); + TextEditSplitter *editManager = TextEditSplitter::instance(); + using namespace std::placeholders; if (windowService) { NavEditMainWindow *navEditWindow = NavEditMainWindow::instance(); - TextEditSplitter *editManager = TextEditSplitter::instance(); navEditWindow->setWidgetEdit(new AbstractCentral(editManager)); windowService->addCentralNavigation(MWNA_EDIT, new AbstractCentral(navEditWindow)); - using namespace std::placeholders; if (!windowService->addWidgetWorkspace) { windowService->addWidgetWorkspace = std::bind(&NavEditMainWindow::addWidgetWorkspace, navEditWindow, _1, _2); } @@ -101,6 +102,35 @@ bool CodeEditor::start() windowService->addContextWidget(tr("&Application Output"), new AbstractWidget(OutputPane::instance()), "Application"); } + QString errStr; + if (!ctx.load(dpfservice::EditorService::name(), &errStr)) { + qCritical() << errStr; + } + EditorService *editorService = dpfGetService(EditorService); + if (editorService) { + if (!editorService->getSelectedText) { + editorService->getSelectedText = std::bind(&TextEditSplitter::getSelectedText, editManager); + } + if (!editorService->getCursorBeforeText) { + editorService->getCursorBeforeText = std::bind(&TextEditSplitter::getCursorBeforeText, editManager); + } + if (!editorService->getCursorAfterText) { + editorService->getCursorAfterText = std::bind(&TextEditSplitter::getCursorAfterText, editManager); + } + if (!editorService->replaceSelectedText) { + editorService->replaceSelectedText = std::bind(&TextEditSplitter::replaceSelectedText, editManager, _1); + } + if (!editorService->showTips) { + editorService->showTips = std::bind(&TextEditSplitter::showTips, editManager, _1); + } + if (!editorService->insertText) { + editorService->insertText = std::bind(&TextEditSplitter::insertText, editManager, _1); + } + if (!editorService->undo) { + editorService->undo = std::bind(&TextEditSplitter::undo, editManager); + } + } + return true; } diff --git a/src/plugins/codeeditor/mainframe/naveditmainwindow.cpp b/src/plugins/codeeditor/mainframe/naveditmainwindow.cpp index 3077aa5bc..b1909ddc8 100644 --- a/src/plugins/codeeditor/mainframe/naveditmainwindow.cpp +++ b/src/plugins/codeeditor/mainframe/naveditmainwindow.cpp @@ -106,6 +106,7 @@ void NavEditMainWindow::addWidgetWorkspace(const QString &title, AbstractWidget qDockWidgetWorkspace->setFeatures(QDockWidget::DockWidgetMovable); qDockWidgetWorkspace->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::DockWidgetArea::LeftDockWidgetArea, qDockWidgetWorkspace); + setCorner(Qt::BottomLeftCorner, Qt::DockWidgetArea::LeftDockWidgetArea); qDockWidgetWorkspace->setWidget(qTabWidgetWorkspace); } diff --git a/src/plugins/codeeditor/mainframe/texteditkeeper.cpp b/src/plugins/codeeditor/mainframe/texteditkeeper.cpp index 2fc167469..d7654cfab 100644 --- a/src/plugins/codeeditor/mainframe/texteditkeeper.cpp +++ b/src/plugins/codeeditor/mainframe/texteditkeeper.cpp @@ -6,7 +6,12 @@ TextEdit *TextEditKeeper::create(const QString &language, QString *err) { - return instance()->editFactory.create(language, err); + TextEdit *textEdit = instance()->editFactory.create(language, err); + connect(textEdit, &TextEdit::focusChanged, [=](bool focused){ + if (focused) + instance()->activeTextEdit = textEdit; + }); + return textEdit; } void TextEditKeeper::setAnalysedLanguage(const QString &lang) @@ -103,3 +108,8 @@ void TextEditKeeper::removeProjectInfo(const dpfservice::ProjectInfo &info) instance()->proInfo = dpfservice::ProjectInfo(); } } + +TextEdit *TextEditKeeper::getActiveTextEdit() +{ + return instance()->activeTextEdit; +} diff --git a/src/plugins/codeeditor/mainframe/texteditkeeper.h b/src/plugins/codeeditor/mainframe/texteditkeeper.h index 77804661a..b507572da 100644 --- a/src/plugins/codeeditor/mainframe/texteditkeeper.h +++ b/src/plugins/codeeditor/mainframe/texteditkeeper.h @@ -10,23 +10,25 @@ #include #include -class TextEditKeeper final +class TextEditKeeper : public QObject { + Q_OBJECT + dpf::QtClassFactory editFactory; QString analysedLanguage; QString analysedWorkspace; QString analysedStorage; AnalysedData data; dpfservice::ProjectInfo proInfo; + TextEdit *activeTextEdit = nullptr; TextEditKeeper(){} +public: inline static TextEditKeeper *instance(){ static TextEditKeeper ins; return &ins; } -public: - template static bool impl(const QString &language = Edit::implLanguage(), QString *err = nullptr) { bool result = true; @@ -55,6 +57,8 @@ class TextEditKeeper final static dpfservice::ProjectInfo projectInfo(); static void saveProjectInfo(const dpfservice::ProjectInfo &info); static void removeProjectInfo(const dpfservice::ProjectInfo &info); + + static TextEdit *getActiveTextEdit(); }; #endif // TEXTEDITKEEPER_H diff --git a/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.cpp b/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.cpp index d421ddba6..7a1802111 100644 --- a/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.cpp +++ b/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.cpp @@ -245,7 +245,8 @@ void ScintillaEditExtern::replaceRange(Scintilla::Position start, clearSelections(); setSelectionStart(start); setSelectionEnd(end); - replaceSel(text.toLatin1()); + auto stdString = text.toStdString(); + replaceSel(stdString.c_str()); emit replaceed(file(), start, end, text); } @@ -531,7 +532,7 @@ void ScintillaEditExtern::sciUpdateAnnotation() void ScintillaEditExtern::keyReleaseEvent(QKeyEvent *event) { - return ScintillaEdit::keyReleaseEvent(event); + ScintillaEdit::keyReleaseEvent(event); } void ScintillaEditExtern::keyPressEvent(QKeyEvent *event) @@ -541,7 +542,10 @@ void ScintillaEditExtern::keyPressEvent(QKeyEvent *event) if (isKeyCtrl && isKeyS) { saveText(); } - return ScintillaEdit::keyPressEvent(event); + + editor.keyPressEvent(event->key()); + + ScintillaEdit::keyPressEvent(event); } void ScintillaEditExtern::sciMarginClicked(Scintilla::Position position, Scintilla::KeyMod modifiers, int margin) @@ -563,9 +567,54 @@ void ScintillaEditExtern::sciMarginClicked(Scintilla::Position position, Scintil } } +QString ScintillaEditExtern::getSelectedText() +{ + return getSelText(); +} + +QString ScintillaEditExtern::getCursorLineText() +{ + int lineMaxCharacter = 100; + return getCurLine(lineMaxCharacter); +} + +QString ScintillaEditExtern::getCursorBeforeText() +{ + int curPosition = static_cast(currentPos()); + return textRange(0, curPosition); +} + +QString ScintillaEditExtern::getCursorAfterText() +{ + int curPosition = static_cast(currentPos()); + return textRange(curPosition, static_cast(length())); +} + +void ScintillaEditExtern::replaceSelectedRange(const QString &text) +{ + replaceRange(selectionStart(), selectionEnd(), text); +} + +void ScintillaEditExtern::showTips(const QString &tips) +{ + sptr_t pos = currentPos(); + auto stdString = tips.toStdString(); + callTipShow(pos, stdString.c_str()); +} + +void ScintillaEditExtern::insertText(const QString &text) +{ + auto curPos = currentPos(); + auto stdString = text.toStdString(); + ScintillaEdit::insertText(curPos, stdString.c_str()); + auto cursorNewPos = curPos + static_cast(stdString.size()); + gotoPos(cursorNewPos); +} + void ScintillaEditExtern::focusInEvent(QFocusEvent *event) { focusChanged(true); + return ScintillaEdit::focusInEvent(event); } diff --git a/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.h b/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.h index 66604e958..aae665e92 100644 --- a/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.h +++ b/src/plugins/codeeditor/textedittabwidget/scintillaeditextern.h @@ -63,6 +63,15 @@ class ScintillaEditExtern : public ScintillaEdit void find(const QString &srcText, int operateType); void replace(const QString &srcText, const QString &destText, int operateType); + // Mozart added. + QString getSelectedText(); + QString getCursorLineText(); + QString getCursorBeforeText(); + QString getCursorAfterText(); + void replaceSelectedRange(const QString &text); + void showTips(const QString &tips); + void insertText(const QString &text); + signals: void hovered(Scintilla::Position position); void hoverCleaned(Scintilla::Position position); diff --git a/src/plugins/codeeditor/textedittabwidget/style/stylelsp.cpp b/src/plugins/codeeditor/textedittabwidget/style/stylelsp.cpp index fd31a333c..27b1f636d 100644 --- a/src/plugins/codeeditor/textedittabwidget/style/stylelsp.cpp +++ b/src/plugins/codeeditor/textedittabwidget/style/stylelsp.cpp @@ -585,6 +585,9 @@ void StyleLsp::sciSelectionMenu(QContextMenuEvent *event) } }); + // notify other plugin to add action. + editor.contextMenu(QVariant::fromValue(&contextMenu)); + contextMenu.move(showPos); contextMenu.exec(); } diff --git a/src/plugins/codeeditor/textedittabwidget/textedit.cpp b/src/plugins/codeeditor/textedittabwidget/textedit.cpp index 62a9587b1..8f60703ef 100644 --- a/src/plugins/codeeditor/textedittabwidget/textedit.cpp +++ b/src/plugins/codeeditor/textedittabwidget/textedit.cpp @@ -8,6 +8,7 @@ #include "SciLexer.h" #include "common/common.h" #include "framework/framework.h" +#include "services/editor/editorservice.h" #include #include @@ -34,6 +35,7 @@ class TextEditPrivate StyleSci *styleSci {nullptr}; }; +using namespace dpfservice; TextEdit::TextEdit(QWidget *parent) : ScintillaEditExtern (parent) , d (new TextEditPrivate) @@ -48,6 +50,10 @@ TextEdit::TextEdit(QWidget *parent) Q_UNUSED(text) Q_UNUSED(line) emit this->fileChanged(this->file()); + auto service = dpfGetService(EditorService); + if (service) { + emit service->fileChanged(); + } }, Qt::UniqueConnection); QObject::connect(this, &ScintillaEditExtern::textDeleted, this, diff --git a/src/plugins/codeeditor/textedittabwidget/texteditsplitter.cpp b/src/plugins/codeeditor/textedittabwidget/texteditsplitter.cpp index 62f83debd..f828a5691 100644 --- a/src/plugins/codeeditor/textedittabwidget/texteditsplitter.cpp +++ b/src/plugins/codeeditor/textedittabwidget/texteditsplitter.cpp @@ -4,6 +4,7 @@ #include "texteditsplitter.h" #include "transceiver/codeeditorreceiver.h" +#include "mainframe/texteditkeeper.h" #include #include @@ -52,6 +53,69 @@ TextEditSplitter::TextEditSplitter(QWidget *parent) } +QString TextEditSplitter::getSelectedText() +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return ""; + + return edit->getSelectedText(); +} + +QString TextEditSplitter::getCursorBeforeText() +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return ""; + + return edit->getCursorBeforeText(); +} + +QString TextEditSplitter::getCursorAfterText() +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return ""; + + return edit->getCursorAfterText(); +} + +void TextEditSplitter::replaceSelectedText(const QString &text) +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return; + + edit->replaceSelectedRange(text); +} + +void TextEditSplitter::showTips(const QString &tips) +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return; + + edit->showTips(tips); +} + +void TextEditSplitter::insertText(const QString &text) +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return; + + edit->insertText(text); +} + +void TextEditSplitter::undo() +{ + auto edit = TextEditKeeper::instance()->getActiveTextEdit(); + if (!edit) + return; + + edit->undo(); +} + TextEditSplitter::~TextEditSplitter() { diff --git a/src/plugins/codeeditor/textedittabwidget/texteditsplitter.h b/src/plugins/codeeditor/textedittabwidget/texteditsplitter.h index 0050faf2b..48c08b96d 100644 --- a/src/plugins/codeeditor/textedittabwidget/texteditsplitter.h +++ b/src/plugins/codeeditor/textedittabwidget/texteditsplitter.h @@ -22,6 +22,14 @@ class TextEditSplitter : public QWidget explicit TextEditSplitter(QWidget *parent = nullptr); QSplitter *getSplitter() const; + QString getSelectedText(); + QString getCursorBeforeText(); + QString getCursorAfterText(); + void replaceSelectedText(const QString &text); + void showTips(const QString &tips); + void insertText(const QString &text); + void undo(); + public slots: void doSplit(Qt::Orientation orientation, const newlsp::ProjectKey &key, const QString &file); void doClose(); diff --git a/src/plugins/codeeditor/textedittabwidget/textedittabbar.cpp b/src/plugins/codeeditor/textedittabwidget/textedittabbar.cpp index 7cdd4c05a..e6bbbd72a 100644 --- a/src/plugins/codeeditor/textedittabwidget/textedittabbar.cpp +++ b/src/plugins/codeeditor/textedittabwidget/textedittabbar.cpp @@ -4,6 +4,8 @@ #include "textedittabbar.h" #include "common/common.h" +#include "services/editor/editorservice.h" + #include #include #include @@ -145,6 +147,7 @@ void TextEditTabBar::doFileChanged(const QString &file) d->tab->setTabText(index , "*" + d->tab->tabText(index)); qInfo() << d->tab->tabText(index); + } void TextEditTabBar::doFileSaved(const QString &file) diff --git a/src/plugins/codeeditor/textedittabwidget/textedittabwidget.h b/src/plugins/codeeditor/textedittabwidget/textedittabwidget.h index bdfa49b48..ad95f5bd0 100644 --- a/src/plugins/codeeditor/textedittabwidget/textedittabwidget.h +++ b/src/plugins/codeeditor/textedittabwidget/textedittabwidget.h @@ -23,6 +23,7 @@ class TextEditTabWidget : public QWidget static TextEditTabWidget *instance(); void setCloseButtonVisible(bool flag); void setSplitButtonVisible(bool flag); + TextEdit *activeTextWidget(); protected: virtual void keyPressEvent(QKeyEvent *event) override; diff --git a/src/plugins/codegeex/CMakeLists.txt b/src/plugins/codegeex/CMakeLists.txt index 21ba68563..36576b6b3 100644 --- a/src/plugins/codegeex/CMakeLists.txt +++ b/src/plugins/codegeex/CMakeLists.txt @@ -6,8 +6,9 @@ set(CXX_CPP codegeex.cpp eventreceiver.cpp codegeex/askapi.cpp - codegeex/copilot.cpp + codegeex/copilotapi.cpp askpage/askpage.cpp + copilot.cpp codegeex.json ) @@ -15,7 +16,8 @@ set(CXX_H codegeex.h eventreceiver.h codegeex/askapi.h - codegeex/copilot.h + codegeex/copilotapi.h + copilot.h askpage/askpage.h ) diff --git a/src/plugins/codegeex/askpage/askpage.cpp b/src/plugins/codegeex/askpage/askpage.cpp index 3a1aed534..e75ccef52 100644 --- a/src/plugins/codegeex/askpage/askpage.cpp +++ b/src/plugins/codegeex/askpage/askpage.cpp @@ -20,18 +20,10 @@ #include #include - // TODO(mozart):get those variable from config pane. -static const char *kApiKey = "f30ea902c3824ee88e221a32363c0823"; -static const char *kUrlGenerateOneLine = "https://tianqi.aminer.cn/api/v2/multilingual_code_generate"; -static const char *kUrlGenerateMultiLine = "https://tianqi.aminer.cn/api/v2/multilingual_code_generate_adapt"; -static const char *kUrlComment = "https://tianqi.aminer.cn/api/v2/multilingual_code_explain"; -static const char *kUrlTranslate = "https://tianqi.aminer.cn/api/v2/multilingual_code_translate"; -static const char *kUrlBugfix = "https://tianqi.aminer.cn/api/v2/multilingual_code_bugfix"; static const char *kUrlSSEChat = "https://codegeex.cn/prod/code/chatGlmSse/chat"; static const char *kUrlNewSession = "https://codegeex.cn/prod/code/chatGlmTalk/insert"; - using namespace CodeGeeX; AskPage::AskPage(QWidget *parent) : QWidget(parent) , timer(new QTimer(this)) diff --git a/src/plugins/codegeex/codegeex.cpp b/src/plugins/codegeex/codegeex.cpp index 127ddb018..01003c86e 100644 --- a/src/plugins/codegeex/codegeex.cpp +++ b/src/plugins/codegeex/codegeex.cpp @@ -7,6 +7,7 @@ #include "common/common.h" #include "services/window/windowservice.h" +#include "copilot.h" #include "base/abstractwidget.h" #include "base/abstractaction.h" @@ -30,10 +31,12 @@ bool CodeGeex::start() // Add widget to left bar if (windowService->addCentralNavigation) { auto askPage = new AskPage(); - windowService->addActionNavigation(title, new AbstractAction(new QAction(QIcon(":/CodeGeex/images/navigation.png"), QAction::tr("CodeGeex")))); - windowService->addCentralNavigation(title, new AbstractCentral(askPage)); + windowService->addWidgetWorkspace(title, new AbstractWidget(askPage)); } } + + Copilot::instance(); + return true; } diff --git a/src/plugins/codegeex/codegeex/askapi.cpp b/src/plugins/codegeex/codegeex/askapi.cpp index c9dd57ba5..c5d4698f1 100644 --- a/src/plugins/codegeex/codegeex/askapi.cpp +++ b/src/plugins/codegeex/codegeex/askapi.cpp @@ -44,7 +44,6 @@ void AskApi::sendQueryRequest(const QString &codeToken) qCritical() << "Error:" << reply->errorString(); } else { QString response = QString::fromUtf8(reply->readAll()); - qInfo() << "Response:" << response; QJsonDocument document = QJsonDocument::fromJson(response.toUtf8()); QJsonObject jsonObject = document.object(); int code = jsonObject["code"].toInt(); diff --git a/src/plugins/codegeex/codegeex/copilot.cpp b/src/plugins/codegeex/codegeex/copilotapi.cpp similarity index 62% rename from src/plugins/codegeex/codegeex/copilot.cpp rename to src/plugins/codegeex/codegeex/copilotapi.cpp index 76bb9fa53..8cefc22f9 100644 --- a/src/plugins/codegeex/codegeex/copilot.cpp +++ b/src/plugins/codegeex/codegeex/copilotapi.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later -#include "copilot.h" +#include "copilotapi.h" #include #include @@ -12,13 +12,23 @@ #include #include +#include + namespace CodeGeeX { -Copilot::Copilot():manager(new QNetworkAccessManager(this)) +QHash kResponseTypes{ + {"multilingual_code_generate", CopilotApi::multilingual_code_generate}, + {"multilingual_code_explain", CopilotApi::multilingual_code_explain}, + {"multilingual_code_translate", CopilotApi::multilingual_code_translate}, + {"multilingual_code_bugfix", CopilotApi::multilingual_code_bugfix} +}; +CopilotApi::CopilotApi(QObject *parent) + : QObject (parent) + , manager(new QNetworkAccessManager(this)) { } -void Copilot::postGenerate(const QString &url, const QString &apiKey, const QString &prompt, const QString &suffix) +void CopilotApi::postGenerate(const QString &url, const QString &apiKey, const QString &prompt, const QString &suffix) { QByteArray body = assembleGenerateBody(prompt, suffix, apiKey); @@ -26,7 +36,7 @@ void Copilot::postGenerate(const QString &url, const QString &apiKey, const QStr processResponse(reply); } -void Copilot::postComment(const QString &url, +void CopilotApi::postComment(const QString &url, const QString &apiKey, const QString &prompt, const QString &lang, @@ -38,7 +48,7 @@ void Copilot::postComment(const QString &url, processResponse(reply); } -void Copilot::postTranslate(const QString &url, +void CopilotApi::postTranslate(const QString &url, const QString &apiKey, const QString &prompt, const QString &src_lang, @@ -50,7 +60,7 @@ void Copilot::postTranslate(const QString &url, processResponse(reply); } -void Copilot::postFixBug(const QString &url, +void CopilotApi::postFixBug(const QString &url, const QString &apiKey, const QString &prompt, const QString &lang, @@ -61,7 +71,7 @@ void Copilot::postFixBug(const QString &url, processResponse(reply); } -QNetworkReply *Copilot::postMessage(const QString &url, const QByteArray &body) +QNetworkReply *CopilotApi::postMessage(const QString &url, const QByteArray &body) { QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -69,7 +79,7 @@ QNetworkReply *Copilot::postMessage(const QString &url, const QByteArray &body) return manager->post(request, body); } -QByteArray Copilot::assembleGenerateBody(const QString &prompt, +QByteArray CopilotApi::assembleGenerateBody(const QString &prompt, const QString &suffix, const QString &apikey, int n, @@ -77,10 +87,10 @@ QByteArray Copilot::assembleGenerateBody(const QString &prompt, { QJsonObject json; json.insert("prompt", prompt); - json.insert("isFimEnabled", true); + json.insert("isFimEnabled", false); json.insert("suffix", suffix); json.insert("n", n); - json.insert("lang", "JavaScript"); + json.insert("lang", "c++"); json.insert("apikey", apikey); json.insert("apisecret", apisecret); @@ -88,7 +98,7 @@ QByteArray Copilot::assembleGenerateBody(const QString &prompt, return doc.toJson(); } -QByteArray Copilot::assembleCommentBody(const QString &prompt, const QString &lang, const QString &locale, const QString &apikey, const QString &apisecret) +QByteArray CopilotApi::assembleCommentBody(const QString &prompt, const QString &lang, const QString &locale, const QString &apikey, const QString &apisecret) { QJsonObject json; json.insert("prompt", prompt); @@ -101,7 +111,7 @@ QByteArray Copilot::assembleCommentBody(const QString &prompt, const QString &la return doc.toJson(); } -QByteArray Copilot::assembleTranslateBody(const QString &prompt, const QString &src_lang, const QString &dst_lang, const QString &apikey, const QString &apisecret) +QByteArray CopilotApi::assembleTranslateBody(const QString &prompt, const QString &src_lang, const QString &dst_lang, const QString &apikey, const QString &apisecret) { QJsonObject json; json.insert("prompt", prompt); @@ -114,7 +124,7 @@ QByteArray Copilot::assembleTranslateBody(const QString &prompt, const QString & return doc.toJson(); } -QByteArray Copilot::assembleBugfixBody(const QString &prompt, +QByteArray CopilotApi::assembleBugfixBody(const QString &prompt, const QString &lang, const QString &apikey, const QString &apisecret) @@ -129,7 +139,7 @@ QByteArray Copilot::assembleBugfixBody(const QString &prompt, return doc.toJson(); } -void Copilot::processResponse(QNetworkReply *reply) +void CopilotApi::processResponse(QNetworkReply *reply) { connect(reply, &QNetworkReply::finished, [=]() { if (reply->error()) { @@ -137,7 +147,20 @@ void Copilot::processResponse(QNetworkReply *reply) } else { QString replyMsg = QString::fromUtf8(reply->readAll()); qInfo() << "Response:" << replyMsg; - emit response(replyMsg); + + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(replyMsg.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + return; + } + + QJsonObject jsonObject = jsonDocument.object(); + QString app = jsonObject.value("result").toObject().value("app").toString(); + QJsonArray codeArray = jsonObject.value("result").toObject().value("output").toObject().value("code").toArray(); + QString code = codeArray.first().toString(); + + emit response(kResponseTypes.value(app), code); } }); } diff --git a/src/plugins/codegeex/codegeex/copilot.h b/src/plugins/codegeex/codegeex/copilotapi.h similarity index 86% rename from src/plugins/codegeex/codegeex/copilot.h rename to src/plugins/codegeex/codegeex/copilotapi.h index 41407c243..1cf206c10 100644 --- a/src/plugins/codegeex/codegeex/copilot.h +++ b/src/plugins/codegeex/codegeex/copilotapi.h @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef COPILOT_H -#define COPILOT_H +#ifndef COPILOTAPI_H +#define COPILOTAPI_H #include @@ -10,12 +10,12 @@ class QNetworkAccessManager; class QNetworkReply; namespace CodeGeeX { -class Copilot : public QObject +class CopilotApi : public QObject { Q_OBJECT public: - Copilot(); + CopilotApi(QObject *parent = nullptr); void postGenerate(const QString &url, const QString &apiKey, const QString &prompt, const QString &suffix); @@ -39,8 +39,16 @@ class Copilot : public QObject const QString &lang, const QString &apisecret = ""); + enum ResponseType + { + multilingual_code_generate, + multilingual_code_explain, + multilingual_code_translate, + multilingual_code_bugfix + }; + signals: - void response(const QString &response); + void response(ResponseType responseType, const QString &response); private: QNetworkReply *postMessage(const QString &url, const QByteArray &body); diff --git a/src/plugins/codegeex/copilot.cpp b/src/plugins/codegeex/copilot.cpp new file mode 100644 index 000000000..b14c42b91 --- /dev/null +++ b/src/plugins/codegeex/copilot.cpp @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#include "copilot.h" +#include "services/editor/editorservice.h" + +#include +#include +#include + +static const char *kApiKey = "f30ea902c3824ee88e221a32363c0823"; +static const char *kUrlGenerateOneLine = "https://tianqi.aminer.cn/api/v2/multilingual_code_generate"; +static const char *kUrlGenerateMultiLine = "https://tianqi.aminer.cn/api/v2/multilingual_code_generate_adapt"; +static const char *kUrlComment = "https://tianqi.aminer.cn/api/v2/multilingual_code_explain"; +static const char *kUrlTranslate = "https://tianqi.aminer.cn/api/v2/multilingual_code_translate"; +static const char *kUrlBugfix = "https://tianqi.aminer.cn/api/v2/multilingual_code_bugfix"; + +using namespace CodeGeeX; +using namespace dpfservice; +Copilot::Copilot(QObject *parent) + : QObject(parent) +{ + editorService = dpfGetService(EditorService); + if (!editorService) { + qFatal("Editor service is null!"); + } + + connect(&copilotApi, &CopilotApi::response, [this](CopilotApi::ResponseType responseType, const QString &response){ + qInfo() << "--------------------------:\n" << response; + switch (responseType) { + case CopilotApi::multilingual_code_explain: + if (editorService->replaceSelectedText) { + editorService->replaceSelectedText(response); + } + break; + case CopilotApi::multilingual_code_generate: + generateResponse = response; + if (editorService->showTips && !response.isEmpty()) { + editorService->showTips(response); + } + break; + case CopilotApi::multilingual_code_translate: + emit translatedResult(response); + break; + default: + ; + } + }); + + timer.setSingleShot(true); + connect(editorService, &EditorService::fileChanged, [this](){ + timer.start(500); + }); + + connect(&timer, &QTimer::timeout, [this](){ + generateCode(); + }); +} + +QString Copilot::selectedText() const +{ + if (!editorService->getSelectedText) + return ""; + + return editorService->getSelectedText(); +} + +Copilot *Copilot::instance() +{ + static Copilot ins; + return &ins; +} + +QMenu *Copilot::getMenu() +{ + QMenu *menu = new QMenu(); + menu->setTitle("CodeGeeX"); + + QAction *addComment = new QAction(tr("add comment")); + QAction *login = new QAction(tr("login")); + QAction *translate = new QAction(tr("translate")); + menu->addAction(addComment); + menu->addAction(login); + menu->addAction(translate); + + connect(addComment, &QAction::triggered, this, &Copilot::addComment); + connect(login, &QAction::triggered, this, &Copilot::login); + connect(translate, &QAction::triggered, this, &Copilot::translate); + + return menu; +} + +void Copilot::translateCode(const QString &code, const QString &dstLanguage) +{ + copilotApi.postTranslate(kUrlTranslate, kApiKey, code, "c++", dstLanguage); +} + +void Copilot::replaceSelectedText(const QString &text) +{ + if (editorService->replaceSelectedText) + editorService->replaceSelectedText(text); +} + +void Copilot::insterText(const QString &text) +{ + if (editorService->insertText) + editorService->insertText(text); +} + +void Copilot::processKeyPressEvent(Qt::Key key) +{ + if (key == Qt::Key_Tab && !generateResponse.isEmpty()) { + insterText(generateResponse); + generateResponse = ""; + } +} + +void Copilot::addComment() +{ + copilotApi.postComment(kUrlComment, + kApiKey, + selectedText(), + "cpp", + "zh-CN"); +} + +void Copilot::generateCode() +{ + QString prompt = editorService->getCursorBeforeText(); + QString suffix = editorService->getCursorAfterText(); + + copilotApi.postGenerate(kUrlGenerateMultiLine, + kApiKey, + prompt, + suffix); +} + +void Copilot::login() +{ +} + +void Copilot::translate() +{ + emit translatingText(selectedText()); +} diff --git a/src/plugins/codegeex/copilot.h b/src/plugins/codegeex/copilot.h new file mode 100644 index 000000000..230a93f4f --- /dev/null +++ b/src/plugins/codegeex/copilot.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef COPILOT_H +#define COPILOT_H + +#include "codegeex/copilotapi.h" + +#include +#include + +class QMenu; +namespace dpfservice { + class EditorService; +} + +class Copilot : public QObject +{ + Q_OBJECT +public: + static Copilot *instance(); + QMenu *getMenu(); + + void translateCode(const QString &code, const QString &dstLanguage); + void replaceSelectedText(const QString &text); + void insterText(const QString &text); + void processKeyPressEvent(Qt::Key key); + +signals: + // the code will be tranlated. + void translatingText(const QString &text); + // the result has been tranlated. + void translatedResult(const QString &result); + +public slots: + void addComment(); + void generateCode(); + void login(); + void translate(); + +private: + explicit Copilot(QObject *parent = nullptr); + QString selectedText() const; + + CodeGeeX::CopilotApi copilotApi; + dpfservice::EditorService *editorService = nullptr; + QString generateResponse; + QTimer timer; +}; + +#endif // COPILOT_H diff --git a/src/plugins/codegeex/eventreceiver.cpp b/src/plugins/codegeex/eventreceiver.cpp index c373c6b30..9c3857ba9 100644 --- a/src/plugins/codegeex/eventreceiver.cpp +++ b/src/plugins/codegeex/eventreceiver.cpp @@ -4,6 +4,9 @@ #include "eventreceiver.h" #include "common/common.h" +#include "copilot.h" + +#include EventReceiverDemo::EventReceiverDemo(QObject *parent) : dpf::EventHandler(parent), dpf::AutoEventHandlerRegister() @@ -17,11 +20,27 @@ dpf::EventHandler::Type EventReceiverDemo::type() QStringList EventReceiverDemo::topics() { - return {T_MENU}; + return {T_MENU, editor.topic}; } void EventReceiverDemo::eventProcess(const dpf::Event &event) { + if (event.topic() == editor.topic) { + QString eventData = event.data().toString(); + if (eventData == "contextMenu") { + QMenu *contextMenu = event.property("menu").value(); + if (!contextMenu) + return; + + QMetaObject::invokeMethod(this, [contextMenu](){ + contextMenu->addMenu(Copilot::instance()->getMenu()); + }); + } else if (eventData == "keyPressEvent") { + int eventType = event.property("event").toInt(); + + Copilot::instance()->processKeyPressEvent(static_cast(eventType)); + } + } } diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 19f1e1db7..38a8e5190 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -36,6 +36,7 @@ set(CXX_H language/languagegenerator.h language/languageservice.h symbol/symbolservice.h + editor/editorservice.h ) add_library( diff --git a/src/services/editor/editorservice.h b/src/services/editor/editorservice.h new file mode 100644 index 000000000..7a01623cd --- /dev/null +++ b/src/services/editor/editorservice.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef EDITORSERVICE_H +#define EDITORSERVICE_H + +#include +#include "base/abstractdebugger.h" + +namespace dpfservice { + +class EditorService final : public dpf::PluginService, + dpf::AutoServiceRegister +{ + Q_OBJECT + Q_DISABLE_COPY(EditorService) + +public: + explicit EditorService(QObject *parent = nullptr) + : dpf::PluginService (parent) + { + + } + + static QString name() + { + return "org.deepin.service.EditorService"; + } + + DPF_INTERFACE(QString, getSelectedText); + DPF_INTERFACE(QString, getCursorBeforeText); + DPF_INTERFACE(QString, getCursorAfterText); + DPF_INTERFACE(void, replaceSelectedText, const QString &); + DPF_INTERFACE(void, insertText, const QString &); + DPF_INTERFACE(void, showTips, const QString &tips); + DPF_INTERFACE(void, undo); + + +signals: + void fileChanged(); +}; + +} // namespace dpfservice +#endif // EDITORSERVICE_H