diff --git a/CMakeLists.txt b/CMakeLists.txt index deb32f3..0b5d121 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,9 @@ add_qtc_plugin(QodeAssist QodeAssist.qrc LSPCompletion.hpp LLMSuggestion.hpp LLMSuggestion.cpp - QodeAssistHoverHandler.hpp QodeAssistHoverHandler.cpp QodeAssistClient.hpp QodeAssistClient.cpp QodeAssistUtils.hpp DocumentContextReader.hpp DocumentContextReader.cpp QodeAssistData.hpp + utils/CounterTooltip.hpp utils/CounterTooltip.cpp ) diff --git a/LLMSuggestion.cpp b/LLMSuggestion.cpp index 39f215c..1fd50f5 100644 --- a/LLMSuggestion.cpp +++ b/LLMSuggestion.cpp @@ -19,13 +19,17 @@ #include "LLMSuggestion.hpp" +#include +#include #include #include +#include namespace QodeAssist { LLMSuggestion::LLMSuggestion(const Completion &completion, QTextDocument *origin) : m_completion(completion) + , m_linesCount(0) { int startPos = completion.range().start().toPositionInDocument(origin); int endPos = completion.range().end().toPositionInDocument(origin); @@ -71,24 +75,30 @@ bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget) bool LLMSuggestion::applyNextLine(TextEditor::TextEditorWidget *widget) { - QTextCursor currentCursor = widget->textCursor(); const QString text = m_completion.text(); + QStringList lines = text.split('\n'); - int endPos = currentCursor.position(); - while (endPos < text.length() && !text[endPos].isSpace()) { - ++endPos; - } + if (m_linesCount < lines.size()) + m_linesCount++; - QString wordToInsert = text.left(endPos); + showTooltip(widget, m_linesCount); - wordToInsert = wordToInsert.split('\n').first(); + return m_linesCount == lines.size() && !Utils::ToolTip::isVisible(); +} - if (!wordToInsert.isEmpty()) { - currentCursor.insertText(wordToInsert); - widget->setTextCursor(currentCursor); - } +void LLMSuggestion::onCounterFinished(int count) +{ + Utils::ToolTip::hide(); + m_linesCount = 0; + QTextCursor cursor = m_completion.range().toSelection(m_start.document()); + cursor.beginEditBlock(); + cursor.removeSelectedText(); - return false; + QStringList lines = m_completion.text().split('\n'); + QString textToInsert = lines.mid(0, count).join('\n'); + + cursor.insertText(textToInsert); + cursor.endEditBlock(); } void LLMSuggestion::reset() @@ -101,4 +111,14 @@ int LLMSuggestion::position() return m_start.position(); } +void LLMSuggestion::showTooltip(TextEditor::TextEditorWidget *widget, int count) +{ + Utils::ToolTip::hide(); + QPoint pos = widget->mapToGlobal(widget->cursorRect().topRight()); + pos += QPoint(-10, -50); + m_counterTooltip = new CounterTooltip(count); + Utils::ToolTip::show(pos, m_counterTooltip, widget); + connect(m_counterTooltip, &CounterTooltip::finished, this, &LLMSuggestion::onCounterFinished); +} + } // namespace QodeAssist diff --git a/LLMSuggestion.hpp b/LLMSuggestion.hpp index de4a9cd..fcf716c 100644 --- a/LLMSuggestion.hpp +++ b/LLMSuggestion.hpp @@ -19,14 +19,17 @@ #pragma once +#include +#include "LSPCompletion.hpp" #include -#include "LSPCompletion.hpp" +#include "utils/CounterTooltip.hpp" namespace QodeAssist { -class LLMSuggestion final : public TextEditor::TextSuggestion +class LLMSuggestion final : public QObject, public TextEditor::TextSuggestion { + Q_OBJECT public: LLMSuggestion(const Completion &completion, QTextDocument *origin); @@ -38,9 +41,15 @@ public: const Completion &completion() const { return m_completion; } + void showTooltip(TextEditor::TextEditorWidget *widget, int count); + void onCounterFinished(int count); + private: Completion m_completion; QTextCursor m_start; + int m_linesCount; + + CounterTooltip *m_counterTooltip = nullptr; }; } // namespace QodeAssist diff --git a/LSPCompletion.hpp b/LSPCompletion.hpp index ea80fd7..7209999 100644 --- a/LSPCompletion.hpp +++ b/LSPCompletion.hpp @@ -43,6 +43,10 @@ public: { return typedValue(LanguageServerProtocol::positionKey); } + void setRange(const LanguageServerProtocol::Range &range) + { + insert(LanguageServerProtocol::rangeKey, range); + } LanguageServerProtocol::Range range() const { return typedValue(LanguageServerProtocol::rangeKey); diff --git a/QodeAssistClient.cpp b/QodeAssistClient.cpp index 260d122..296c519 100644 --- a/QodeAssistClient.cpp +++ b/QodeAssistClient.cpp @@ -190,7 +190,6 @@ void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &r return; editor->insertSuggestion( std::make_unique(completions.first(), editor->document())); - editor->addHoverHandler(&m_hoverHandler); } } @@ -237,11 +236,6 @@ void QodeAssistClient::cleanupConnections() disconnect(m_documentOpenedConnection); disconnect(m_documentClosedConnection); - for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) { - if (auto textEditor = qobject_cast(editor)) - textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler); - } - qDeleteAll(m_scheduledRequests); m_scheduledRequests.clear(); } diff --git a/QodeAssistClient.hpp b/QodeAssistClient.hpp index 73c1c7b..0271839 100644 --- a/QodeAssistClient.hpp +++ b/QodeAssistClient.hpp @@ -27,7 +27,6 @@ #include #include "LSPCompletion.hpp" -#include "QodeAssistHoverHandler.hpp" namespace QodeAssist { @@ -54,7 +53,6 @@ private: QHash m_runningRequests; QHash m_scheduledRequests; - QodeAssistHoverHandler m_hoverHandler; QMetaObject::Connection m_documentOpenedConnection; QMetaObject::Connection m_documentClosedConnection; }; diff --git a/QodeAssistHoverHandler.cpp b/QodeAssistHoverHandler.cpp deleted file mode 100644 index 849f8f7..0000000 --- a/QodeAssistHoverHandler.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2023 The Qt Company Ltd. - * Copyright (C) 2024 Petr Mironychev - * - * This file is part of Qode Assist. - * - * The Qt Company portions: - * SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - * - * Petr Mironychev portions: - * 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 "QodeAssistHoverHandler.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "LLMSuggestion.hpp" -#include "LSPCompletion.hpp" -#include "QodeAssisttr.h" - -using namespace TextEditor; -using namespace LanguageServerProtocol; -using namespace Utils; - -namespace QodeAssist { - -class QodeAssistCompletionToolTip : public QToolBar -{ -public: - QodeAssistCompletionToolTip(TextEditorWidget *editor) - : m_editor(editor) - { - auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString())); - connect(apply, &QAction::triggered, this, &QodeAssistCompletionToolTip::apply); - - auto applyWord = addAction(Tr::tr("Apply Next Line (%1)") - .arg(QKeySequence(QKeySequence::MoveToNextLine).toString())); - connect(applyWord, &QAction::triggered, this, &QodeAssistCompletionToolTip::applyWord); - qDebug() << "applyWord sequence" << applyWord->shortcut(); - } - -private: - void apply() - { - if (auto *suggestion = dynamic_cast(m_editor->currentSuggestion())) { - if (!suggestion->apply()) - return; - } - ToolTip::hide(); - } - - void applyWord() - { - if (auto *suggestion = dynamic_cast(m_editor->currentSuggestion())) { - if (!suggestion->applyWord(m_editor)) - return; - } - ToolTip::hide(); - } - - TextEditorWidget *m_editor; -}; - -void QodeAssistHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, - int pos, - ReportPriority report) -{ - QScopeGuard cleanup([&] { report(Priority_None); }); - if (!editorWidget->suggestionVisible()) - return; - - QTextCursor cursor(editorWidget->document()); - cursor.setPosition(pos); - m_block = cursor.block(); - auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); - - if (!suggestion) - return; - - const Completion completion = suggestion->completion(); - if (completion.text().isEmpty()) - return; - - cleanup.dismiss(); - report(Priority_Suggestion); -} - -void QodeAssistHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, - const QPoint &point) -{ - Q_UNUSED(point) - auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); - - if (!suggestion) - return; - - auto tooltipWidget = new QodeAssistCompletionToolTip(editorWidget); - - const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor()); - QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft()) - - Utils::ToolTip::offsetFromPosition(); - pos.ry() -= tooltipWidget->sizeHint().height(); - ToolTip::show(pos, tooltipWidget, editorWidget); -} - -} // namespace QodeAssist diff --git a/utils/CounterTooltip.cpp b/utils/CounterTooltip.cpp new file mode 100644 index 0000000..ec5a699 --- /dev/null +++ b/utils/CounterTooltip.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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 "CounterTooltip.hpp" + +namespace QodeAssist { + +CounterTooltip::CounterTooltip(int count) + : m_count(count) +{ + m_label = new QLabel(this); + addWidget(m_label); + updateLabel(); + + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + m_timer->setInterval(2000); + + connect(m_timer, &QTimer::timeout, this, [this] { emit finished(m_count); }); + + m_timer->start(); +} + +CounterTooltip::~CounterTooltip() {} + +void CounterTooltip::updateLabel() +{ + const auto hotkey = QKeySequence(QKeySequence::MoveToNextWord).toString(); + m_label->setText(QString("Insert Next %1 line(s) (%2)").arg(m_count).arg(hotkey)); +} + +} // namespace QodeAssist diff --git a/QodeAssistHoverHandler.hpp b/utils/CounterTooltip.hpp similarity index 52% rename from QodeAssistHoverHandler.hpp rename to utils/CounterTooltip.hpp index 30733a2..5e53e00 100644 --- a/QodeAssistHoverHandler.hpp +++ b/utils/CounterTooltip.hpp @@ -1,13 +1,8 @@ -/* - * Copyright (C) 2023 The Qt Company Ltd. +/* * Copyright (C) 2024 Petr Mironychev * - * This file is part of Qode Assist. + * This file is part of QodeAssist. * - * The Qt Company portions: - * SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - * - * Petr Mironychev portions: * 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 @@ -24,24 +19,30 @@ #pragma once -#include -#include +#include +#include +#include +#include namespace QodeAssist { -class QodeAssistHoverHandler : public TextEditor::BaseHoverHandler +class CounterTooltip : public QToolBar { -public: - QodeAssistHoverHandler() = default; + Q_OBJECT -protected: - void identifyMatch(TextEditor::TextEditorWidget *editorWidget, - int pos, - ReportPriority report) final; - void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final; +public: + CounterTooltip(int count); + ~CounterTooltip(); + +signals: + void finished(int count); private: - QTextBlock m_block; + void updateLabel(); + + QLabel *m_label; + QTimer *m_timer; + int m_count; }; } // namespace QodeAssist