From ab55cbe3dee739bd6afc52d180b122a08827c837 Mon Sep 17 00:00:00 2001 From: Liu Zhangjian Date: Mon, 21 Oct 2024 10:00:07 +0800 Subject: [PATCH 1/2] feat: [completion] Optimize intelligent completion as title Log: Optimize intelligent completion --- src/base/abstractinlinecompletionprovider.cpp | 10 +++ src/base/abstractinlinecompletionprovider.h | 54 ++++++++++++ src/plugins/codeeditor/codeeditor.cpp | 3 +- .../codeeditor/gui/private/texteditor_p.cpp | 85 ++++++++++++++++++- .../codeeditor/gui/private/texteditor_p.h | 8 +- src/plugins/codeeditor/gui/tabwidget.cpp | 6 -- src/plugins/codeeditor/gui/tabwidget.h | 1 - src/plugins/codeeditor/gui/texteditor.cpp | 55 ++---------- src/plugins/codeeditor/gui/texteditor.h | 3 - .../codeeditor/gui/workspacewidget.cpp | 8 -- src/plugins/codeeditor/gui/workspacewidget.h | 1 - .../codeeditor/utils/resourcemanager.cpp | 45 ++++++++++ .../codeeditor/utils/resourcemanager.h | 26 ++++++ .../codegeex/codegeexcompletionprovider.cpp | 60 +++++++++++++ .../codegeex/codegeexcompletionprovider.h | 37 ++++++++ src/plugins/codegeex/copilot.cpp | 46 ++++------ src/plugins/codegeex/copilot.h | 7 +- src/plugins/codegeex/eventreceiver.cpp | 6 -- src/plugins/codegeex/eventreceiver.h | 1 - src/services/editor/editorservice.h | 3 +- 20 files changed, 351 insertions(+), 114 deletions(-) create mode 100644 src/base/abstractinlinecompletionprovider.cpp create mode 100644 src/base/abstractinlinecompletionprovider.h create mode 100644 src/plugins/codeeditor/utils/resourcemanager.cpp create mode 100644 src/plugins/codeeditor/utils/resourcemanager.h create mode 100644 src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp create mode 100644 src/plugins/codegeex/codegeex/codegeexcompletionprovider.h diff --git a/src/base/abstractinlinecompletionprovider.cpp b/src/base/abstractinlinecompletionprovider.cpp new file mode 100644 index 000000000..53b4c7705 --- /dev/null +++ b/src/base/abstractinlinecompletionprovider.cpp @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "abstractinlinecompletionprovider.h" + +AbstractInlineCompletionProvider::AbstractInlineCompletionProvider(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/base/abstractinlinecompletionprovider.h b/src/base/abstractinlinecompletionprovider.h new file mode 100644 index 000000000..b26a844e8 --- /dev/null +++ b/src/base/abstractinlinecompletionprovider.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ABSTRACTINLINECOMPLETIONPROVIDER_H +#define ABSTRACTINLINECOMPLETIONPROVIDER_H + +#include + +class AbstractInlineCompletionProvider : public QObject +{ + Q_OBJECT +public: + struct Position + { + int line = -1; + int column = -1; + + bool operator==(const Position &pos) const + { + return line == pos.line && column == pos.column; + } + + bool operator!=(const Position &pos) const + { + return !(operator==(pos)); + } + }; + + struct InlineCompletionContext + { + QString prefix; + QString suffix; + }; + + struct InlineCompletionItem + { + QString completion; + Position pos; + }; + + explicit AbstractInlineCompletionProvider(QObject *parent = nullptr); + + virtual QString providerName() const = 0; + virtual void provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &contex) = 0; + virtual QList inlineCompletionItems() const = 0; + virtual bool inlineCompletionEnabled() const { return false; } + +Q_SIGNALS: + void finished(); +}; + +Q_DECLARE_METATYPE(AbstractInlineCompletionProvider::InlineCompletionItem) +#endif // ABSTRACTINLINECOMPLETIONPROVIDER_H diff --git a/src/plugins/codeeditor/codeeditor.cpp b/src/plugins/codeeditor/codeeditor.cpp index 27d4fd7de..5f888dd90 100644 --- a/src/plugins/codeeditor/codeeditor.cpp +++ b/src/plugins/codeeditor/codeeditor.cpp @@ -10,6 +10,7 @@ #include "gui/settings/editorsettingswidget.h" #include "lexer/lexermanager.h" #include "utils/editorutils.h" +#include "utils/resourcemanager.h" #include "status/statusinfomanager.h" #include "symbol/symbollocator.h" #include "symbol/symbolwidget.h" @@ -171,7 +172,6 @@ void CodeEditor::initEditorService() editorService->undo = std::bind(&WorkspaceWidget::undo, workspaceWidget); editorService->modifiedFiles = std::bind(&WorkspaceWidget::modifiedFiles, workspaceWidget); editorService->saveAll = std::bind(&WorkspaceWidget::saveAll, workspaceWidget); - editorService->setCompletion = std::bind(&WorkspaceWidget::setCompletion, workspaceWidget, _1); editorService->currentFile = std::bind(&WorkspaceWidget::currentFile, workspaceWidget); editorService->setText = std::bind(&WorkspaceWidget::setText, workspaceWidget, _1); editorService->registerWidget = std::bind(&WorkspaceWidget::registerWidget, workspaceWidget, _1, _2); @@ -201,6 +201,7 @@ void CodeEditor::initEditorService() editorService->rangeText = std::bind(&WorkspaceWidget::rangeText, workspaceWidget, _1, _2); editorService->selectionRange = std::bind(&WorkspaceWidget::selectionRange, workspaceWidget, _1); editorService->codeRange = std::bind(&WorkspaceWidget::codeRange, workspaceWidget, _1, _2); + editorService->registerInlineCompletionProvider = std::bind(&ResourceManager::registerInlineCompletionProvider, ResourceManager::instance(), _1); LexerManager::instance()->init(editorService); } diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.cpp b/src/plugins/codeeditor/gui/private/texteditor_p.cpp index 9153a771e..c4a5b8545 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.cpp +++ b/src/plugins/codeeditor/gui/private/texteditor_p.cpp @@ -5,6 +5,7 @@ #include "texteditor_p.h" #include "utils/editorutils.h" #include "utils/colordefine.h" +#include "utils/resourcemanager.h" #include "lexer/lexermanager.h" #include "transceiver/codeeditorreceiver.h" #include "common/common.h" @@ -229,6 +230,36 @@ void TextEditorPrivate::onSelectionChanged() editor.selectionChanged(fileName, lineFrom, indexFrom, lineTo, indexTo); } +void TextEditorPrivate::setInlineCompletion() +{ + auto provider = dynamic_cast(sender()); + if (!provider) + return; + + // Take only one completion for now + const auto &items = provider->inlineCompletionItems(); + for (const auto &item : items) { + if (item.completion.isEmpty()) + continue; + + AbstractInlineCompletionProvider::Position pos; + q->getCursorPosition(&pos.line, &pos.column); + if (item.pos != pos) + continue; + + cancelInlineCompletion(); + inlineCompletionCache = qMakePair(pos.line, item.completion); + const auto &part1 = item.completion.mid(0, item.completion.indexOf('\n')); + const auto &part2 = item.completion.mid(item.completion.indexOf('\n') + 1); + QsciStyle cpStyle(1, "", Qt::gray, q->lexer() ? q->lexer()->defaultPaper(-1) : q->paper(), + q->lexer() ? q->lexer()->defaultFont() : q->font()); + q->eOLAnnotate(pos.line, part1, cpStyle); + if (part1 != part2) + q->annotate(pos.line, part2, cpStyle); + return; + } +} + void TextEditorPrivate::loadLexer() { if (fileName.isEmpty()) @@ -551,10 +582,10 @@ void TextEditorPrivate::updateCacheInfo(int pos, int added) } } - if (cpCache.first != -1 && cpCache.first >= line) { - const auto &eolStr = q->eolAnnotation(cpCache.first); - if (eolStr.isEmpty() || !cpCache.second.contains(eolStr)) - cpCache.first += added; + if (inlineCompletionCache.first != -1 && inlineCompletionCache.first >= line) { + const auto &eolStr = q->eolAnnotation(inlineCompletionCache.first); + if (eolStr.isEmpty() || !inlineCompletionCache.second.contains(eolStr)) + inlineCompletionCache.first += added; } // update eolannotation line @@ -577,6 +608,50 @@ void TextEditorPrivate::updateCacheInfo(int pos, int added) } } +void TextEditorPrivate::provideInlineCompletion(int pos, int added) +{ + auto providerList = ResourceManager::instance()->inlineCompletionProviders(); + for (auto provider : providerList) { + if (!provider || !provider->inlineCompletionEnabled()) + continue; + + int cursorPos = pos + added; + AbstractInlineCompletionProvider::Position position; + q->lineIndexFromPosition(cursorPos, &position.line, &position.column); + int lineEndPos = q->SendScintilla(TextEditor::SCI_GETLINEENDPOSITION, position.line); + if (lineEndPos != cursorPos) + return; + + connect(provider, &AbstractInlineCompletionProvider::finished, + this, &TextEditorPrivate::setInlineCompletion, Qt::UniqueConnection); + AbstractInlineCompletionProvider::InlineCompletionContext context; + context.prefix = q->text(0, cursorPos); + context.suffix = q->text(cursorPos, q->length()); + provider->provideInlineCompletionItems(position, context); + } +} + +void TextEditorPrivate::applyInlineCompletion() +{ + if (inlineCompletionCache.first == -1) + return; + + const auto cpStr = inlineCompletionCache.second; + cancelInlineCompletion(); + q->insertText(cpStr); +} + +void TextEditorPrivate::cancelInlineCompletion() +{ + if (inlineCompletionCache.first == -1) + return; + + q->clearEOLAnnotations(inlineCompletionCache.first); + q->clearAnnotations(inlineCompletionCache.first); + + inlineCompletionCache = qMakePair(-1, QString()); +} + void TextEditorPrivate::resetThemeColor() { if (q->lexer()) { @@ -628,6 +703,8 @@ void TextEditorPrivate::onModified(int pos, int mtype, const QString &text, int updateCacheInfo(pos, added); if (mtype & TextEditor::SC_MOD_INSERTTEXT) { + if (!(mtype & (TextEditor::SC_PERFORMED_UNDO | TextEditor::SC_PERFORMED_REDO))) + provideInlineCompletion(pos, len); emit q->textAdded(pos, len, added, text, line); } else if (mtype & TextEditor::SC_MOD_DELETETEXT) { emit q->textRemoved(pos, len, -added, text, line); diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.h b/src/plugins/codeeditor/gui/private/texteditor_p.h index e802b587b..146e6c2d0 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.h +++ b/src/plugins/codeeditor/gui/private/texteditor_p.h @@ -10,6 +10,8 @@ #include "lsp/languageclienthandler.h" #include "gui/completion/codecompletionwidget.h" +#include "base/abstractinlinecompletionprovider.h" + #include #include @@ -68,6 +70,9 @@ class TextEditorPrivate : public QObject void setContainerWidget(QWidget *widget); void updateLineWidgetPosition(); void updateCacheInfo(int pos, int added); + void provideInlineCompletion(int pos, int added); + void applyInlineCompletion(); + void cancelInlineCompletion(); public slots: void resetThemeColor(); @@ -77,6 +82,7 @@ public slots: int line, int foldNow, int foldPrev, int token, int annotationLinesAdded); void updateSettings(); void onSelectionChanged(); + void setInlineCompletion(); public: TextEditor *q { nullptr }; @@ -98,7 +104,7 @@ public slots: int fontSize { 10 }; using CompletionCache = QPair; - CompletionCache cpCache { -1, "" }; + CompletionCache inlineCompletionCache { -1, "" }; CodeCompletionWidget *completionWidget { nullptr }; QMap commentSettings; diff --git a/src/plugins/codeeditor/gui/tabwidget.cpp b/src/plugins/codeeditor/gui/tabwidget.cpp index d41659206..90ab6ea42 100644 --- a/src/plugins/codeeditor/gui/tabwidget.cpp +++ b/src/plugins/codeeditor/gui/tabwidget.cpp @@ -728,12 +728,6 @@ void TabWidget::undo() editor->undo(); } -void TabWidget::setCompletion(const QString &info) -{ - if (auto editor = d->currentTextEditor()) - editor->setCompletion(info); -} - void TabWidget::gotoNextPosition() { if (d->nextPosRecord.isEmpty()) diff --git a/src/plugins/codeeditor/gui/tabwidget.h b/src/plugins/codeeditor/gui/tabwidget.h index 4a5f32685..ce3ed9d50 100644 --- a/src/plugins/codeeditor/gui/tabwidget.h +++ b/src/plugins/codeeditor/gui/tabwidget.h @@ -50,7 +50,6 @@ class TabWidget : public QWidget Q_INVOKABLE void showTips(const QString &tips); Q_INVOKABLE void insertText(const QString &text); Q_INVOKABLE void undo(); - Q_INVOKABLE void setCompletion(const QString &info); void gotoNextPosition(); void gotoPreviousPosition(); QString lineText(const QString &fileName, int line); diff --git a/src/plugins/codeeditor/gui/texteditor.cpp b/src/plugins/codeeditor/gui/texteditor.cpp index 262247d43..0065792dc 100644 --- a/src/plugins/codeeditor/gui/texteditor.cpp +++ b/src/plugins/codeeditor/gui/texteditor.cpp @@ -716,49 +716,6 @@ void TextEditor::renameSymbol() d->languageClient->renameActionTriggered(); } -void TextEditor::setCompletion(const QString &info) -{ - if (info.isEmpty()) - return; - - int line = -1, index = -1; - getCursorPosition(&line, &index); - int lineEndPos = SendScintilla(SCI_GETLINEENDPOSITION, line); - if (lineEndPos != cursorPosition()) - return; - - cancelCompletion(); - d->cpCache = qMakePair(line, info); - const auto &part1 = info.mid(0, info.indexOf('\n')); - const auto &part2 = info.mid(info.indexOf('\n') + 1); - QsciStyle cpStyle(1, "", Qt::gray, lexer() ? lexer()->defaultPaper(-1) : paper(), - lexer() ? lexer()->defaultFont() : font()); - eOLAnnotate(line, part1, cpStyle); - if (part1 != part2) - annotate(line, part2, cpStyle); -} - -void TextEditor::applyCompletion() -{ - if (d->cpCache.first == -1) - return; - - const auto cpStr = d->cpCache.second; - cancelCompletion(); - insertText(cpStr); -} - -void TextEditor::cancelCompletion() -{ - if (d->cpCache.first == -1) - return; - - clearEOLAnnotations(d->cpCache.first); - clearAnnotations(d->cpCache.first); - - d->cpCache = qMakePair(-1, QString()); -} - QString TextEditor::cursorBeforeText() const { int pos = d->cursorPosition(); @@ -864,7 +821,7 @@ void TextEditor::onCursorPositionChanged(int line, int index) { Q_UNUSED(line) - cancelCompletion(); + d->cancelInlineCompletion(); editor.cursorPositionChanged(d->fileName, line, index); int pos = positionFromLineIndex(line, index); @@ -883,7 +840,7 @@ void TextEditor::focusOutEvent(QFocusEvent *event) if (!d->lineWidgetContainer->hasFocus()) closeLineWidget(); - cancelCompletion(); + d->cancelInlineCompletion(); Q_EMIT focusOut(); Q_EMIT followTypeEnd(); QsciScintilla::focusOutEvent(event); @@ -891,8 +848,8 @@ void TextEditor::focusOutEvent(QFocusEvent *event) void TextEditor::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Tab && d->cpCache.first != -1) - return applyCompletion(); + if (event->key() == Qt::Key_Tab && d->inlineCompletionCache.first != -1) + return d->applyInlineCompletion(); if (event->key() == Qt::Key_Escape && d->lineWidgetContainer->isVisible()) return closeLineWidget(); @@ -900,8 +857,8 @@ void TextEditor::keyPressEvent(QKeyEvent *event) if (d->completionWidget->processKeyPressEvent(event)) return; - if (event->key() == Qt::Key_Escape && d->cpCache.first != -1) - return cancelCompletion(); + if (event->key() == Qt::Key_Escape && d->inlineCompletionCache.first != -1) + return d->cancelInlineCompletion(); QsciScintilla::keyPressEvent(event); } diff --git a/src/plugins/codeeditor/gui/texteditor.h b/src/plugins/codeeditor/gui/texteditor.h index 82c9f119c..24649bd4d 100644 --- a/src/plugins/codeeditor/gui/texteditor.h +++ b/src/plugins/codeeditor/gui/texteditor.h @@ -100,9 +100,6 @@ class TextEditor : public QsciScintilla void followSymbolUnderCursor(); void findUsage(); void renameSymbol(); - void setCompletion(const QString &info); - void applyCompletion(); - void cancelCompletion(); QString cursorBeforeText() const; QString cursorBehindText() const; diff --git a/src/plugins/codeeditor/gui/workspacewidget.cpp b/src/plugins/codeeditor/gui/workspacewidget.cpp index cbf5857c5..33cdf55e1 100644 --- a/src/plugins/codeeditor/gui/workspacewidget.cpp +++ b/src/plugins/codeeditor/gui/workspacewidget.cpp @@ -1123,14 +1123,6 @@ void WorkspaceWidget::showTips(const QString &tips) Q_ARG(const QString &, tips)); } -void WorkspaceWidget::setCompletion(const QString &info) -{ - if (auto tabWidget = d->currentTabWidget()) - QMetaObject::invokeMethod(tabWidget, "setCompletion", - Qt::QueuedConnection, - Q_ARG(const QString &, info)); -} - void WorkspaceWidget::insertText(const QString &text) { if (auto tabWidget = d->currentTabWidget()) diff --git a/src/plugins/codeeditor/gui/workspacewidget.h b/src/plugins/codeeditor/gui/workspacewidget.h index 0e7fa045b..321ed93e9 100644 --- a/src/plugins/codeeditor/gui/workspacewidget.h +++ b/src/plugins/codeeditor/gui/workspacewidget.h @@ -35,7 +35,6 @@ class WorkspaceWidget : public QWidget void saveAs(const QString &from, const QString &to = ""); void replaceSelectedText(const QString &text); void showTips(const QString &tips); - void setCompletion(const QString &info); void insertText(const QString &text); void undo(); void reloadFile(const QString &fileName); diff --git a/src/plugins/codeeditor/utils/resourcemanager.cpp b/src/plugins/codeeditor/utils/resourcemanager.cpp new file mode 100644 index 000000000..4c4167435 --- /dev/null +++ b/src/plugins/codeeditor/utils/resourcemanager.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "resourcemanager.h" +#include "base/abstractinlinecompletionprovider.h" + +#include + +class ResourceManagerPrivate +{ +public: + QList providerList; +}; + +ResourceManager::ResourceManager() + : d(new ResourceManagerPrivate) +{ +} + +ResourceManager::~ResourceManager() +{ + delete d; +} + +ResourceManager *ResourceManager::instance() +{ + static ResourceManager ins; + return &ins; +} + +void ResourceManager::registerInlineCompletionProvider(AbstractInlineCompletionProvider *provider) +{ + if (d->providerList.contains(provider)) { + qWarning() << "This provider has registered, name: " << provider->providerName(); + return; + } + + d->providerList << provider; +} + +QList ResourceManager::inlineCompletionProviders() const +{ + return d->providerList; +} diff --git a/src/plugins/codeeditor/utils/resourcemanager.h b/src/plugins/codeeditor/utils/resourcemanager.h new file mode 100644 index 000000000..8d68ad8d5 --- /dev/null +++ b/src/plugins/codeeditor/utils/resourcemanager.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RESOURCEMANAGER_H +#define RESOURCEMANAGER_H + +#include "base/abstractinlinecompletionprovider.h" + +class ResourceManagerPrivate; +class ResourceManager +{ +public: + static ResourceManager *instance(); + + void registerInlineCompletionProvider(AbstractInlineCompletionProvider *provider); + QList inlineCompletionProviders() const; + +private: + ResourceManager(); + ~ResourceManager(); + + ResourceManagerPrivate *const d; +}; + +#endif // RESOURCEMANAGER_H diff --git a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp b/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp new file mode 100644 index 000000000..a15112eb8 --- /dev/null +++ b/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexcompletionprovider.h" +#include "copilot.h" + +using namespace CodeGeeX; + +CodeGeeXCompletionProvider::CodeGeeXCompletionProvider(QObject *parent) + : AbstractInlineCompletionProvider(parent) +{ + timer.setSingleShot(true); + timer.setInterval(500); +} + +QString CodeGeeXCompletionProvider::providerName() const +{ + return "CodeGeeX"; +} + +void CodeGeeXCompletionProvider::provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) +{ + positon = pos; + context = c; + connect(&timer, &QTimer::timeout, Copilot::instance(), &Copilot::generateCode, Qt::UniqueConnection); + timer.start(); +} + +QList CodeGeeXCompletionProvider::inlineCompletionItems() const +{ + return completionItems; +} + +void CodeGeeXCompletionProvider::setInlineCompletionEnabled(bool enabled) +{ + if (!enabled && timer.isActive()) + timer.stop(); + + completionEnabled = enabled; +} + +void CodeGeeXCompletionProvider::setInlineCompletions(const QStringList &completions) +{ + completionItems.clear(); + for (const auto &completion : completions) { + InlineCompletionItem item { completion, positon }; + completionItems << item; + } +} + +bool CodeGeeXCompletionProvider::inlineCompletionEnabled() const +{ + return completionEnabled; +} + +AbstractInlineCompletionProvider::InlineCompletionContext CodeGeeXCompletionProvider::inlineCompletionContext() const +{ + return context; +} diff --git a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.h b/src/plugins/codegeex/codegeex/codegeexcompletionprovider.h new file mode 100644 index 000000000..f49b8d85a --- /dev/null +++ b/src/plugins/codegeex/codegeex/codegeexcompletionprovider.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CODEGEEXCOMPLETIONPROVIDER_H +#define CODEGEEXCOMPLETIONPROVIDER_H + +#include "base/abstractinlinecompletionprovider.h" + +#include + +namespace CodeGeeX { +class CodeGeeXCompletionProvider : public AbstractInlineCompletionProvider +{ + Q_OBJECT +public: + explicit CodeGeeXCompletionProvider(QObject *parent = nullptr); + + QString providerName() const override; + void provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) override; + QList inlineCompletionItems() const override; + bool inlineCompletionEnabled() const override; + + InlineCompletionContext inlineCompletionContext() const; + void setInlineCompletionEnabled(bool enabled); + void setInlineCompletions(const QStringList &completions); + +private: + Position positon; + InlineCompletionContext context; + QList completionItems; + QAtomicInteger completionEnabled { false }; + QTimer timer; +}; +} // namespace CodeGeeX + +#endif // CODEGEEXCOMPLETIONPROVIDER_H diff --git a/src/plugins/codegeex/copilot.cpp b/src/plugins/codegeex/copilot.cpp index e3f51663e..1880e9e39 100644 --- a/src/plugins/codegeex/copilot.cpp +++ b/src/plugins/codegeex/copilot.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "copilot.h" #include "widgets/inlinechatwidget.h" +#include "codegeex/codegeexcompletionprovider.h" #include "services/editor/editorservice.h" #include "services/option/optionmanager.h" #include "services/window/windowservice.h" @@ -35,6 +36,8 @@ Copilot::Copilot(QObject *parent) } generateTimer = new QTimer(this); generateTimer->setSingleShot(true); + completionProvider = new CodeGeeXCompletionProvider(this); + editorService->registerInlineCompletionProvider(completionProvider); QAction *lineChatAct = new QAction(tr("Inline Chat"), this); lineChatCmd = ActionManager::instance()->registerAction(lineChatAct, "CodeGeeX.InlineChat"); @@ -60,10 +63,9 @@ Copilot::Copilot(QObject *parent) completion = response; } - if (editorService->setCompletion) { - editorService->setCompletion(completion); - generatedCode = completion; - } + generatedCode = completion; + completionProvider->setInlineCompletions({ completion }); + emit completionProvider->finished(); } break; case CopilotApi::multilingual_code_translate: @@ -157,12 +159,12 @@ void Copilot::setGenerateCodeEnabled(bool enabled) { if (!enabled && generateTimer->isActive()) generateTimer->stop(); - generateCodeEnabled = enabled; + completionProvider->setInlineCompletionEnabled(enabled); } bool Copilot::getGenerateCodeEnabled() const { - return generateCodeEnabled; + return completionProvider->inlineCompletionEnabled(); } void Copilot::setLocale(const QString &locale) @@ -180,17 +182,6 @@ void Copilot::setCurrentModel(CodeGeeX::languageModel model) copilotApi.setModel(model); } -void Copilot::handleTextChanged() -{ - if (!generateCodeEnabled) - return; - - editorService->setCompletion(""); - QMetaObject::invokeMethod(this, [this]() { - generateTimer->start(500); - }); -} - void Copilot::handleSelectionChanged(const QString &fileName, int lineFrom, int indexFrom, int lineTo, int indexTo) { QMetaObject::invokeMethod(this, [=] { @@ -219,23 +210,20 @@ void Copilot::addComment() void Copilot::generateCode() { - if (!generateCodeEnabled) + if (!completionProvider->inlineCompletionEnabled()) return; - QString prefix = editorService->getCursorBeforeText(); - QString suffix = editorService->getCursorBehindText(); - if (!prefix.endsWith(generatedCode) || generateCache.isEmpty()) { - generateType = checkPrefixType(prefix); + const auto &context = completionProvider->inlineCompletionContext(); + if (!context.prefix.endsWith(generatedCode) || generateCache.isEmpty()) { + generateType = checkPrefixType(context.prefix); copilotApi.postGenerate(kUrlGenerateMultiLine, - prefix, - suffix, + context.prefix, + context.suffix, generateType); } else { - QString completion = extractSingleLine(); - if (editorService->setCompletion) { - editorService->setCompletion(completion); - generatedCode = completion; - } + generatedCode = extractSingleLine(); + completionProvider->setInlineCompletions({ generatedCode }); + emit completionProvider->finished(); } } diff --git a/src/plugins/codegeex/copilot.h b/src/plugins/codegeex/copilot.h index 42e063dec..8ea50ae4e 100644 --- a/src/plugins/codegeex/copilot.h +++ b/src/plugins/codegeex/copilot.h @@ -15,6 +15,9 @@ namespace dpfservice { class EditorService; } +namespace CodeGeeX { +class CodeGeeXCompletionProvider; +} class InlineChatWidget; class Command; class Copilot : public QObject @@ -32,7 +35,6 @@ class Copilot : public QObject void setLocale(const QString &locale); void setCommitsLocale(const QString &locale); void setCurrentModel(CodeGeeX::languageModel model); - void handleTextChanged(); void handleSelectionChanged(const QString &fileName, int lineFrom, int indexFrom, int lineTo, int indexTo); @@ -76,10 +78,9 @@ public slots: QString generatedCode {}; QString extractSingleLine(); + CodeGeeX::CodeGeeXCompletionProvider *completionProvider = nullptr; CodeGeeX::CopilotApi::GenerateType generateType; CodeGeeX::CopilotApi::GenerateType checkPrefixType(const QString &prefixCode); - - QAtomicInteger generateCodeEnabled = true; }; #endif // COPILOT_H diff --git a/src/plugins/codegeex/eventreceiver.cpp b/src/plugins/codegeex/eventreceiver.cpp index 7e52b3b85..eeb589498 100644 --- a/src/plugins/codegeex/eventreceiver.cpp +++ b/src/plugins/codegeex/eventreceiver.cpp @@ -15,7 +15,6 @@ CodeGeeXReceiver::CodeGeeXReceiver(QObject *parent) { using namespace std::placeholders; eventHandleMap.insert(editor.contextMenu.name, std::bind(&CodeGeeXReceiver::processContextMenuEvent, this, _1)); - eventHandleMap.insert(editor.textChanged.name, std::bind(&CodeGeeXReceiver::processTextChangedEvent, this, _1)); eventHandleMap.insert(editor.selectionChanged.name, std::bind(&CodeGeeXReceiver::processSelectionChangedEvent, this, _1)); eventHandleMap.insert(notifyManager.actionInvoked.name, std::bind(&CodeGeeXReceiver::processActionInvokedEvent, this, _1)); eventHandleMap.insert(project.openProject.name, std::bind(&CodeGeeXReceiver::processOpenProjectEvent, this, _1)); @@ -52,11 +51,6 @@ void CodeGeeXReceiver::processContextMenuEvent(const dpf::Event &event) }); } -void CodeGeeXReceiver::processTextChangedEvent(const dpf::Event &event) -{ - Copilot::instance()->handleTextChanged(); -} - void CodeGeeXReceiver::processSelectionChangedEvent(const dpf::Event &event) { QString fileName = event.property("fileName").toString(); diff --git a/src/plugins/codegeex/eventreceiver.h b/src/plugins/codegeex/eventreceiver.h index f65ee5987..76281064c 100644 --- a/src/plugins/codegeex/eventreceiver.h +++ b/src/plugins/codegeex/eventreceiver.h @@ -26,7 +26,6 @@ class CodeGeeXReceiver : public dpf::EventHandler, dpf::AutoEventHandlerRegister virtual void eventProcess(const dpf::Event &event) override; void processContextMenuEvent(const dpf::Event &event); - void processTextChangedEvent(const dpf::Event &event); void processSelectionChangedEvent(const dpf::Event &event); void processActionInvokedEvent(const dpf::Event &event); void processOpenProjectEvent(const dpf::Event &event); diff --git a/src/services/editor/editorservice.h b/src/services/editor/editorservice.h index 1bad0d0c8..349082404 100644 --- a/src/services/editor/editorservice.h +++ b/src/services/editor/editorservice.h @@ -10,6 +10,7 @@ #include "base/abstractdebugger.h" #include "base/abstractlexerproxy.h" #include "base/abstracteditwidget.h" +#include "base/abstractinlinecompletionprovider.h" namespace dpfservice { @@ -40,7 +41,6 @@ class EditorService final : public dpf::PluginService, DPF_INTERFACE(void, showTips, const QString &tips); DPF_INTERFACE(void, undo); DPF_INTERFACE(void, setText, const QString &text); - DPF_INTERFACE(void, setCompletion, const QString &info); DPF_INTERFACE(QString, currentFile); DPF_INTERFACE(QStringList, openedFiles); DPF_INTERFACE(QString, fileText, const QString &file); @@ -80,6 +80,7 @@ class EditorService final : public dpf::PluginService, using RepairToolInfo = QMap; DPF_INTERFACE(void, registerDiagnosticRepairTool, const QString &toolName, RepairCallback callback); DPF_INTERFACE(RepairToolInfo, getDiagnosticRepairTool); + DPF_INTERFACE(void, registerInlineCompletionProvider, AbstractInlineCompletionProvider *provider); }; } // namespace dpfservice From ca8aded0ad243b2bf8e7b0a6adfbd73cbc24c43f Mon Sep 17 00:00:00 2001 From: Liu Zhangjian Date: Mon, 21 Oct 2024 19:23:40 +0800 Subject: [PATCH 2/2] feat: [completion] Optimize completions display as title Log: Optimize completions display --- .../gui/completion/codecompletionwidget.cpp | 39 ++++++++++++++---- .../gui/completion/codecompletionwidget.h | 14 +++++-- .../codeeditor/gui/private/texteditor_p.cpp | 40 +++++++++++++++---- .../codeeditor/gui/private/texteditor_p.h | 1 + src/plugins/codeeditor/gui/texteditor.cpp | 4 +- 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp b/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp index bf21b26d2..fa7e35571 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp +++ b/src/plugins/codeeditor/gui/completion/codecompletionwidget.cpp @@ -356,27 +356,43 @@ void CodeCompletionWidget::updateHeight() } geom.setHeight(baseHeight); - if (geometry() != geom) + if (geometry() != geom) { setGeometry(geom); + if (completionOrigin == Top) + updatePosition(); + } QSize entryListSize = QSize(completionView->width(), baseHeight - 2 * frameWidth()); if (completionView->size() != entryListSize) completionView->resize(entryListSize); } -void CodeCompletionWidget::updatePosition(bool force) +void CodeCompletionWidget::updatePosition(bool force, CompletionOrigin origin) { if (!force && !isCompletionActive()) return; - auto cursorPosition = editor()->pointFromPosition(editor()->cursorPosition()); - if (cursorPosition == QPoint(-1, -1)) { - abortCompletion(); - return; + if (origin != completionOrigin) + completionOrigin = origin; + + if (showPosition == QPoint(-1, -1)) { + showPosition = editor()->pointFromPosition(editor()->cursorPosition()); + if (showPosition == QPoint(-1, -1)) { + abortCompletion(); + return; + } + } + + QPoint p = editor()->mapToGlobal(showPosition); + switch (origin) { + case Bottom: + p.setY(p.y() + editor()->fontMetrics().height() + 2); + break; + case Top: + p.setY(p.y() - height()); + break; } - QPoint p = editor()->mapToGlobal(cursorPosition); - p.setY(p.y() + editor()->fontMetrics().height() + 2); move(p); } @@ -478,6 +494,13 @@ void CodeCompletionWidget::focusOutEvent(QFocusEvent *event) abortCompletion(); } +void CodeCompletionWidget::hideEvent(QHideEvent *event) +{ + showPosition = QPoint(-1, -1); + completionOrigin = Bottom; + QFrame::hideEvent(event); +} + void CodeCompletionWidget::onTextAdded(int pos, int len, int added, const QString &text, int line) { Q_UNUSED(line) diff --git a/src/plugins/codeeditor/gui/completion/codecompletionwidget.h b/src/plugins/codeeditor/gui/completion/codecompletionwidget.h index 4cb37ba72..7e1b95430 100644 --- a/src/plugins/codeeditor/gui/completion/codecompletionwidget.h +++ b/src/plugins/codeeditor/gui/completion/codecompletionwidget.h @@ -21,6 +21,11 @@ class CodeCompletionWidget : public QFrame { Q_OBJECT public: + enum CompletionOrigin { + Bottom = 0, + Top + }; + explicit CodeCompletionWidget(TextEditor *parent); TextEditor *editor() const; @@ -29,7 +34,7 @@ class CodeCompletionWidget : public QFrame void startCompletion(); void updateHeight(); - void updatePosition(bool force = false); + void updatePosition(bool force = false, CompletionOrigin origin = Bottom); void setCompletion(const QString &info, const QIcon &icon, const QKeySequence &key); public slots: @@ -40,7 +45,8 @@ public slots: void automaticInvocation(); protected: - virtual void focusOutEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void hideEvent(QHideEvent *event) override; private: void initUI(); @@ -68,14 +74,14 @@ private slots: CodeCompletionModel *completionModel { nullptr }; CompletionSortFilterProxyModel *proxyModel { nullptr }; CodeCompletionExtendWidget *completionExtWidget { nullptr }; - QTimer *automaticInvocationTimer { nullptr }; QString automaticInvocationLine; int automaticInvocationAt; - bool needShow { false }; bool isCompletionInput { false }; + QPoint showPosition { -1, -1 }; + CompletionOrigin completionOrigin { Bottom }; }; #endif // CODECOMPLETIONWIDGET_H diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.cpp b/src/plugins/codeeditor/gui/private/texteditor_p.cpp index c4a5b8545..9e7ad5586 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.cpp +++ b/src/plugins/codeeditor/gui/private/texteditor_p.cpp @@ -57,6 +57,7 @@ void TextEditorPrivate::init() TextEditor::SC_CASEINSENSITIVEBEHAVIOUR_IGNORECASE); selectionChangeTimer.setSingleShot(true); selectionChangeTimer.setInterval(50); + completionWidget->installEventFilter(q); initWidgetContainer(); initMargins(); @@ -249,13 +250,7 @@ void TextEditorPrivate::setInlineCompletion() cancelInlineCompletion(); inlineCompletionCache = qMakePair(pos.line, item.completion); - const auto &part1 = item.completion.mid(0, item.completion.indexOf('\n')); - const auto &part2 = item.completion.mid(item.completion.indexOf('\n') + 1); - QsciStyle cpStyle(1, "", Qt::gray, q->lexer() ? q->lexer()->defaultPaper(-1) : q->paper(), - q->lexer() ? q->lexer()->defaultFont() : q->font()); - q->eOLAnnotate(pos.line, part1, cpStyle); - if (part1 != part2) - q->annotate(pos.line, part2, cpStyle); + updateInlineCompletion(); return; } } @@ -652,6 +647,37 @@ void TextEditorPrivate::cancelInlineCompletion() inlineCompletionCache = qMakePair(-1, QString()); } +void TextEditorPrivate::updateInlineCompletion() +{ + int line = inlineCompletionCache.first; + QString completion = inlineCompletionCache.second; + if (line == -1) + return; + + const auto &part1 = completion.mid(0, completion.indexOf('\n')); + QString part2 = completion.mid(completion.indexOf('\n') + 1); + QsciStyle cpStyle(1, "", Qt::gray, q->lexer() ? q->lexer()->defaultPaper(-1) : q->paper(), + q->lexer() ? q->lexer()->defaultFont() : q->font()); + q->eOLAnnotate(line, part1, cpStyle); + if (part1 != part2) { + if (!completionWidget->isCompletionActive()) { + q->annotate(line, part2, cpStyle); + return; + } + + auto point = q->pointFromPosition(q->cursorPosition()); + if (point.y() >= completionWidget->height()) { + completionWidget->updatePosition(true, CodeCompletionWidget::Top); + } else { + auto lineHeight = q->textHeight(line); + auto padding = completionWidget->height() / lineHeight; + part2.prepend(QString(padding, '\n')); + } + + q->annotate(line, part2, cpStyle); + } +} + void TextEditorPrivate::resetThemeColor() { if (q->lexer()) { diff --git a/src/plugins/codeeditor/gui/private/texteditor_p.h b/src/plugins/codeeditor/gui/private/texteditor_p.h index 146e6c2d0..afde07ed8 100644 --- a/src/plugins/codeeditor/gui/private/texteditor_p.h +++ b/src/plugins/codeeditor/gui/private/texteditor_p.h @@ -73,6 +73,7 @@ class TextEditorPrivate : public QObject void provideInlineCompletion(int pos, int added); void applyInlineCompletion(); void cancelInlineCompletion(); + void updateInlineCompletion(); public slots: void resetThemeColor(); diff --git a/src/plugins/codeeditor/gui/texteditor.cpp b/src/plugins/codeeditor/gui/texteditor.cpp index 0065792dc..2d9bde449 100644 --- a/src/plugins/codeeditor/gui/texteditor.cpp +++ b/src/plugins/codeeditor/gui/texteditor.cpp @@ -850,7 +850,7 @@ void TextEditor::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Tab && d->inlineCompletionCache.first != -1) return d->applyInlineCompletion(); - + if (event->key() == Qt::Key_Escape && d->lineWidgetContainer->isVisible()) return closeLineWidget(); @@ -897,6 +897,8 @@ bool TextEditor::eventFilter(QObject *obj, QEvent *event) d->updateLineWidgetPosition(); } else if (obj == d->mainWindow() && event->type() == QEvent::Move) { d->updateLineWidgetPosition(); + } else if (obj == d->completionWidget && event->type() == QEvent::Hide) { + d->updateInlineCompletion(); } return QsciScintilla::eventFilter(obj, event);