2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2017 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
#include "cppeditorwidget.h"
|
|
|
|
|
|
2021-08-30 10:58:08 +02:00
|
|
|
#include "cppcodeformatter.h"
|
|
|
|
|
#include "cppcompletionassistprovider.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
#include "cppeditorconstants.h"
|
|
|
|
|
#include "cppeditordocument.h"
|
2021-08-30 10:58:08 +02:00
|
|
|
#include "cppeditoroutline.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
#include "cppeditorplugin.h"
|
2023-01-11 20:43:10 +01:00
|
|
|
#include "cppeditortr.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
#include "cppfunctiondecldeflink.h"
|
|
|
|
|
#include "cpplocalrenaming.h"
|
2021-08-30 10:58:08 +02:00
|
|
|
#include "cppmodelmanager.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
#include "cpppreprocessordialog.h"
|
|
|
|
|
#include "cppquickfixassistant.h"
|
2023-01-11 20:43:10 +01:00
|
|
|
#include "cppselectionchanger.h"
|
|
|
|
|
#include "cppsemanticinfo.h"
|
2021-08-30 10:58:08 +02:00
|
|
|
#include "cpptoolssettings.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
#include "cppuseselectionsupdater.h"
|
2023-01-11 20:43:10 +01:00
|
|
|
#include "doxygengenerator.h"
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
|
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
|
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
|
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2017-09-19 15:38:20 +02:00
|
|
|
#include <coreplugin/find/searchresultwindow.h>
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2021-10-26 17:49:54 +02:00
|
|
|
#include <projectexplorer/projectnodes.h>
|
|
|
|
|
#include <projectexplorer/projecttree.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
|
|
|
|
|
2017-10-26 11:35:50 +02:00
|
|
|
#include <texteditor/basefilefind.h>
|
2017-08-02 14:39:45 +02:00
|
|
|
#include <texteditor/behaviorsettings.h>
|
|
|
|
|
#include <texteditor/codeassist/assistproposalitem.h>
|
|
|
|
|
#include <texteditor/codeassist/genericproposal.h>
|
|
|
|
|
#include <texteditor/codeassist/genericproposalmodel.h>
|
2017-12-15 10:36:16 +01:00
|
|
|
#include <texteditor/codeassist/iassistprocessor.h>
|
2021-09-02 08:51:22 +02:00
|
|
|
#include <texteditor/commentssettings.h>
|
2017-08-02 14:39:45 +02:00
|
|
|
#include <texteditor/completionsettings.h>
|
|
|
|
|
#include <texteditor/fontsettings.h>
|
|
|
|
|
#include <texteditor/refactoroverlay.h>
|
|
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
#include <texteditor/textdocumentlayout.h>
|
|
|
|
|
#include <texteditor/texteditorsettings.h>
|
|
|
|
|
|
|
|
|
|
#include <cplusplus/ASTPath.h>
|
|
|
|
|
#include <cplusplus/FastPreprocessor.h>
|
|
|
|
|
#include <cplusplus/MatchingText.h>
|
2023-01-11 20:43:10 +01:00
|
|
|
|
2020-06-17 12:23:44 +02:00
|
|
|
#include <utils/infobar.h>
|
2017-09-15 15:16:33 +02:00
|
|
|
#include <utils/progressindicator.h>
|
2017-08-02 14:39:45 +02:00
|
|
|
#include <utils/qtcassert.h>
|
2017-09-15 15:16:33 +02:00
|
|
|
#include <utils/textutils.h>
|
2017-08-02 14:39:45 +02:00
|
|
|
#include <utils/utilsicons.h>
|
|
|
|
|
|
|
|
|
|
#include <QAction>
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QElapsedTimer>
|
|
|
|
|
#include <QMenu>
|
|
|
|
|
#include <QPointer>
|
|
|
|
|
#include <QTextEdit>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QToolButton>
|
2017-09-15 15:16:33 +02:00
|
|
|
#include <QWidgetAction>
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
enum { UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL = 200 };
|
|
|
|
|
|
|
|
|
|
using namespace Core;
|
|
|
|
|
using namespace CPlusPlus;
|
2021-10-26 17:49:54 +02:00
|
|
|
using namespace ProjectExplorer;
|
2017-08-02 14:39:45 +02:00
|
|
|
using namespace TextEditor;
|
2020-06-26 13:59:38 +02:00
|
|
|
using namespace Utils;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
namespace CppEditor {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2021-09-02 08:51:22 +02:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
bool isStartOfDoxygenComment(const QTextCursor &cursor)
|
|
|
|
|
{
|
|
|
|
|
const int pos = cursor.position();
|
|
|
|
|
|
|
|
|
|
QTextDocument *document = cursor.document();
|
|
|
|
|
QString comment = QString(document->characterAt(pos - 3))
|
|
|
|
|
+ document->characterAt(pos - 2)
|
|
|
|
|
+ document->characterAt(pos - 1);
|
|
|
|
|
|
|
|
|
|
return comment == QLatin1String("/**")
|
|
|
|
|
|| comment == QLatin1String("/*!")
|
|
|
|
|
|| comment == QLatin1String("///")
|
|
|
|
|
|| comment == QLatin1String("//!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DoxygenGenerator::DocumentationStyle doxygenStyle(const QTextCursor &cursor,
|
|
|
|
|
const QTextDocument *doc)
|
|
|
|
|
{
|
|
|
|
|
const int pos = cursor.position();
|
|
|
|
|
|
|
|
|
|
QString comment = QString(doc->characterAt(pos - 3))
|
|
|
|
|
+ doc->characterAt(pos - 2)
|
|
|
|
|
+ doc->characterAt(pos - 1);
|
|
|
|
|
|
|
|
|
|
if (comment == QLatin1String("/**"))
|
|
|
|
|
return DoxygenGenerator::JavaStyle;
|
|
|
|
|
else if (comment == QLatin1String("/*!"))
|
|
|
|
|
return DoxygenGenerator::QtStyle;
|
|
|
|
|
else if (comment == QLatin1String("///"))
|
|
|
|
|
return DoxygenGenerator::CppStyleA;
|
|
|
|
|
else
|
|
|
|
|
return DoxygenGenerator::CppStyleB;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if previous line is a CppStyle Doxygen Comment
|
|
|
|
|
bool isPreviousLineCppStyleComment(const QTextCursor &cursor)
|
|
|
|
|
{
|
|
|
|
|
const QTextBlock ¤tBlock = cursor.block();
|
|
|
|
|
if (!currentBlock.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QTextBlock &actual = currentBlock.previous();
|
|
|
|
|
if (!actual.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QString text = actual.text().trimmed();
|
|
|
|
|
return text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if next line is a CppStyle Doxygen Comment
|
|
|
|
|
bool isNextLineCppStyleComment(const QTextCursor &cursor)
|
|
|
|
|
{
|
|
|
|
|
const QTextBlock ¤tBlock = cursor.block();
|
|
|
|
|
if (!currentBlock.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QTextBlock &actual = currentBlock.next();
|
|
|
|
|
if (!actual.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QString text = actual.text().trimmed();
|
|
|
|
|
return text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isCppStyleContinuation(const QTextCursor& cursor)
|
|
|
|
|
{
|
|
|
|
|
return isPreviousLineCppStyleComment(cursor) || isNextLineCppStyleComment(cursor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool lineStartsWithCppDoxygenCommentAndCursorIsAfter(const QTextCursor &cursor,
|
|
|
|
|
const QTextDocument *doc)
|
|
|
|
|
{
|
|
|
|
|
QTextCursor cursorFirstNonBlank(cursor);
|
|
|
|
|
cursorFirstNonBlank.movePosition(QTextCursor::StartOfLine);
|
|
|
|
|
while (doc->characterAt(cursorFirstNonBlank.position()).isSpace()
|
|
|
|
|
&& cursorFirstNonBlank.movePosition(QTextCursor::NextCharacter)) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QTextBlock& block = cursorFirstNonBlank.block();
|
|
|
|
|
const QString text = block.text().trimmed();
|
|
|
|
|
if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!")))
|
|
|
|
|
return (cursor.position() >= cursorFirstNonBlank.position() + 3);
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isCursorAfterNonNestedCppStyleComment(const QTextCursor &cursor,
|
|
|
|
|
TextEditor::TextEditorWidget *editorWidget)
|
|
|
|
|
{
|
|
|
|
|
QTextDocument *document = editorWidget->document();
|
|
|
|
|
QTextCursor cursorBeforeCppComment(cursor);
|
|
|
|
|
while (document->characterAt(cursorBeforeCppComment.position()) != QLatin1Char('/')
|
|
|
|
|
&& cursorBeforeCppComment.movePosition(QTextCursor::PreviousCharacter)) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cursorBeforeCppComment.movePosition(QTextCursor::PreviousCharacter))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (document->characterAt(cursorBeforeCppComment.position()) != QLatin1Char('/'))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!cursorBeforeCppComment.movePosition(QTextCursor::PreviousCharacter))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return !CPlusPlus::MatchingText::isInCommentHelper(cursorBeforeCppComment);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleDoxygenCppStyleContinuation(QTextCursor &cursor)
|
|
|
|
|
{
|
|
|
|
|
const int blockPos = cursor.positionInBlock();
|
|
|
|
|
const QString &text = cursor.block().text();
|
|
|
|
|
int offset = 0;
|
|
|
|
|
for (; offset < blockPos; ++offset) {
|
|
|
|
|
if (!text.at(offset).isSpace())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the line does not start with the comment we don't
|
|
|
|
|
// consider it as a continuation. Handles situations like:
|
|
|
|
|
// void d(); ///<enter>
|
|
|
|
|
if (offset + 3 > text.size())
|
|
|
|
|
return false;
|
|
|
|
|
const QStringView commentMarker = QStringView(text).mid(offset, 3);
|
|
|
|
|
if (commentMarker != QLatin1String("///") && commentMarker != QLatin1String("//!"))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
QString newLine(QLatin1Char('\n'));
|
|
|
|
|
newLine.append(text.left(offset)); // indent correctly
|
|
|
|
|
newLine.append(commentMarker.toString());
|
|
|
|
|
newLine.append(QLatin1Char(' '));
|
|
|
|
|
|
|
|
|
|
cursor.insertText(newLine);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleDoxygenContinuation(QTextCursor &cursor,
|
|
|
|
|
TextEditor::TextEditorWidget *editorWidget,
|
|
|
|
|
const bool enableDoxygen,
|
|
|
|
|
const bool leadingAsterisks)
|
|
|
|
|
{
|
|
|
|
|
const QTextDocument *doc = editorWidget->document();
|
|
|
|
|
|
|
|
|
|
// It might be a continuation if:
|
|
|
|
|
// a) current line starts with /// or //! and cursor is positioned after the comment
|
|
|
|
|
// b) current line is in the middle of a multi-line Qt or Java style comment
|
|
|
|
|
|
|
|
|
|
if (!cursor.atEnd()) {
|
|
|
|
|
if (enableDoxygen && lineStartsWithCppDoxygenCommentAndCursorIsAfter(cursor, doc))
|
|
|
|
|
return handleDoxygenCppStyleContinuation(cursor);
|
|
|
|
|
|
|
|
|
|
if (isCursorAfterNonNestedCppStyleComment(cursor, editorWidget))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We continue the comment if the cursor is after a comment's line asterisk and if
|
|
|
|
|
// there's no asterisk immediately after the cursor (that would already be considered
|
|
|
|
|
// a leading asterisk).
|
|
|
|
|
int offset = 0;
|
|
|
|
|
const int blockPos = cursor.positionInBlock();
|
|
|
|
|
const QString ¤tLine = cursor.block().text();
|
|
|
|
|
for (; offset < blockPos; ++offset) {
|
|
|
|
|
if (!currentLine.at(offset).isSpace())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In case we don't need to insert leading asteriskses, this code will be run once (right after
|
|
|
|
|
// hitting enter on the line containing '/*'). It will insert a continuation without an
|
|
|
|
|
// asterisk, but with an extra space. After that, the normal indenting will take over and do the
|
|
|
|
|
// Right Thing <TM>.
|
|
|
|
|
if (offset < blockPos
|
|
|
|
|
&& (currentLine.at(offset) == QLatin1Char('*')
|
|
|
|
|
|| (offset < blockPos - 1
|
|
|
|
|
&& currentLine.at(offset) == QLatin1Char('/')
|
|
|
|
|
&& currentLine.at(offset + 1) == QLatin1Char('*')))) {
|
|
|
|
|
// Ok, so the line started with an '*' or '/*'
|
|
|
|
|
int followinPos = blockPos;
|
|
|
|
|
// Now search for the first non-whitespace character to align to:
|
|
|
|
|
for (; followinPos < currentLine.length(); ++followinPos) {
|
|
|
|
|
if (!currentLine.at(followinPos).isSpace())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (followinPos == currentLine.length() // a)
|
|
|
|
|
|| currentLine.at(followinPos) != QLatin1Char('*')) { // b)
|
|
|
|
|
// So either a) the line ended after a '*' and we need to insert a continuation, or
|
|
|
|
|
// b) we found the start of some text and we want to align the continuation to that.
|
|
|
|
|
QString newLine(QLatin1Char('\n'));
|
|
|
|
|
QTextCursor c(cursor);
|
|
|
|
|
c.movePosition(QTextCursor::StartOfBlock);
|
|
|
|
|
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, offset);
|
|
|
|
|
newLine.append(c.selectedText());
|
|
|
|
|
if (currentLine.at(offset) == QLatin1Char('/')) {
|
|
|
|
|
if (leadingAsterisks)
|
|
|
|
|
newLine.append(QLatin1String(" * "));
|
|
|
|
|
else
|
|
|
|
|
newLine.append(QLatin1String(" "));
|
|
|
|
|
offset += 3;
|
|
|
|
|
} else {
|
|
|
|
|
// If '*' is not within a comment, skip.
|
|
|
|
|
QTextCursor cursorOnFirstNonWhiteSpace(cursor);
|
|
|
|
|
const int positionOnFirstNonWhiteSpace = cursor.position() - blockPos + offset;
|
|
|
|
|
cursorOnFirstNonWhiteSpace.setPosition(positionOnFirstNonWhiteSpace);
|
|
|
|
|
if (!CPlusPlus::MatchingText::isInCommentHelper(cursorOnFirstNonWhiteSpace))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// ...otherwise do the continuation
|
|
|
|
|
int start = offset;
|
|
|
|
|
while (offset < blockPos && currentLine.at(offset) == QLatin1Char('*'))
|
|
|
|
|
++offset;
|
|
|
|
|
const QChar ch = leadingAsterisks ? QLatin1Char('*') : QLatin1Char(' ');
|
|
|
|
|
newLine.append(QString(offset - start, ch));
|
|
|
|
|
}
|
|
|
|
|
for (; offset < blockPos && currentLine.at(offset) == ' '; ++offset)
|
|
|
|
|
newLine.append(QLatin1Char(' '));
|
|
|
|
|
cursor.insertText(newLine);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool trySplitComment(TextEditor::TextEditorWidget *editorWidget,
|
|
|
|
|
const CPlusPlus::Snapshot &snapshot)
|
|
|
|
|
{
|
|
|
|
|
const TextEditor::CommentsSettings &settings = CppToolsSettings::instance()->commentsSettings();
|
|
|
|
|
if (!settings.m_enableDoxygen && !settings.m_leadingAsterisks)
|
|
|
|
|
return false;
|
|
|
|
|
|
2021-06-28 09:13:57 +02:00
|
|
|
if (editorWidget->multiTextCursor().hasMultipleCursors())
|
|
|
|
|
return false;
|
|
|
|
|
|
2021-09-02 08:51:22 +02:00
|
|
|
QTextCursor cursor = editorWidget->textCursor();
|
|
|
|
|
if (!CPlusPlus::MatchingText::isInCommentHelper(cursor))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// We are interested on two particular cases:
|
|
|
|
|
// 1) The cursor is right after a /**, /*!, /// or ///! and the user pressed enter.
|
|
|
|
|
// If Doxygen is enabled we need to generate an entire comment block.
|
|
|
|
|
// 2) The cursor is already in the middle of a multi-line comment and the user pressed
|
|
|
|
|
// enter. If leading asterisk(s) is set we need to write a comment continuation
|
|
|
|
|
// with those.
|
|
|
|
|
|
|
|
|
|
if (settings.m_enableDoxygen && cursor.positionInBlock() >= 3) {
|
|
|
|
|
const int pos = cursor.position();
|
|
|
|
|
if (isStartOfDoxygenComment(cursor)) {
|
|
|
|
|
QTextDocument *textDocument = editorWidget->document();
|
|
|
|
|
DoxygenGenerator::DocumentationStyle style = doxygenStyle(cursor, textDocument);
|
|
|
|
|
|
|
|
|
|
// Check if we're already in a CppStyle Doxygen comment => continuation
|
|
|
|
|
// Needs special handling since CppStyle does not have start and end markers
|
|
|
|
|
if ((style == DoxygenGenerator::CppStyleA || style == DoxygenGenerator::CppStyleB)
|
|
|
|
|
&& isCppStyleContinuation(cursor)) {
|
|
|
|
|
return handleDoxygenCppStyleContinuation(cursor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DoxygenGenerator doxygen;
|
|
|
|
|
doxygen.setStyle(style);
|
|
|
|
|
doxygen.setAddLeadingAsterisks(settings.m_leadingAsterisks);
|
|
|
|
|
doxygen.setGenerateBrief(settings.m_generateBrief);
|
|
|
|
|
doxygen.setStartComment(false);
|
|
|
|
|
|
|
|
|
|
// Move until we reach any possibly meaningful content.
|
|
|
|
|
while (textDocument->characterAt(cursor.position()).isSpace()
|
|
|
|
|
&& cursor.movePosition(QTextCursor::NextCharacter)) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cursor.atEnd()) {
|
|
|
|
|
const QString &comment = doxygen.generate(cursor,
|
|
|
|
|
snapshot,
|
|
|
|
|
editorWidget->textDocument()->filePath());
|
|
|
|
|
if (!comment.isEmpty()) {
|
|
|
|
|
cursor.beginEditBlock();
|
|
|
|
|
cursor.setPosition(pos);
|
|
|
|
|
cursor.insertText(comment);
|
|
|
|
|
cursor.setPosition(pos - 3, QTextCursor::KeepAnchor);
|
|
|
|
|
editorWidget->textDocument()->autoIndent(cursor);
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
cursor.setPosition(pos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // right after first doxygen comment
|
|
|
|
|
|
|
|
|
|
return handleDoxygenContinuation(cursor,
|
|
|
|
|
editorWidget,
|
|
|
|
|
settings.m_enableDoxygen,
|
|
|
|
|
settings.m_leadingAsterisks);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
class CppEditorWidgetPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
CppEditorWidgetPrivate(CppEditorWidget *q);
|
|
|
|
|
|
2022-05-10 17:21:59 +02:00
|
|
|
bool shouldOfferOutline() const { return !CppModelManager::usesClangd(m_cppEditorDocument); }
|
2021-07-06 15:50:50 +02:00
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
public:
|
|
|
|
|
QPointer<CppModelManager> m_modelManager;
|
|
|
|
|
|
|
|
|
|
CppEditorDocument *m_cppEditorDocument;
|
2021-06-08 14:28:25 +02:00
|
|
|
CppEditorOutline *m_cppEditorOutline = nullptr;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
QTimer m_updateFunctionDeclDefLinkTimer;
|
|
|
|
|
SemanticInfo m_lastSemanticInfo;
|
|
|
|
|
|
|
|
|
|
FunctionDeclDefLinkFinder *m_declDefLinkFinder;
|
|
|
|
|
QSharedPointer<FunctionDeclDefLink> m_declDefLink;
|
|
|
|
|
|
|
|
|
|
QAction *m_parseContextAction = nullptr;
|
|
|
|
|
ParseContextWidget *m_parseContextWidget = nullptr;
|
|
|
|
|
QToolButton *m_preprocessorButton = nullptr;
|
|
|
|
|
|
2017-08-03 14:13:42 +02:00
|
|
|
CppLocalRenaming m_localRenaming;
|
|
|
|
|
CppUseSelectionsUpdater m_useSelectionsUpdater;
|
2017-08-02 14:39:45 +02:00
|
|
|
CppSelectionChanger m_cppSelectionChanger;
|
2021-09-01 18:08:54 +02:00
|
|
|
bool inTestMode = false;
|
2017-08-02 14:39:45 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q)
|
|
|
|
|
: m_modelManager(CppModelManager::instance())
|
|
|
|
|
, m_cppEditorDocument(qobject_cast<CppEditorDocument *>(q->textDocument()))
|
2017-08-03 14:13:42 +02:00
|
|
|
, m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q))
|
2017-08-02 14:39:45 +02:00
|
|
|
, m_localRenaming(q)
|
|
|
|
|
, m_useSelectionsUpdater(q)
|
|
|
|
|
, m_cppSelectionChanger()
|
|
|
|
|
{}
|
2021-09-01 18:08:54 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
|
|
|
|
|
using namespace Internal;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
CppEditorWidget::CppEditorWidget()
|
|
|
|
|
: d(new CppEditorWidgetPrivate(this))
|
|
|
|
|
{
|
2021-08-30 10:58:08 +02:00
|
|
|
qRegisterMetaType<SemanticInfo>("SemanticInfo");
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::finalizeInitialization()
|
|
|
|
|
{
|
|
|
|
|
d->m_cppEditorDocument = qobject_cast<CppEditorDocument *>(textDocument());
|
|
|
|
|
|
2021-08-30 10:58:08 +02:00
|
|
|
setLanguageSettingsId(Constants::CPP_SETTINGS_ID);
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
|
// function combo box sorting
|
2021-07-06 15:50:50 +02:00
|
|
|
d->m_cppEditorOutline = new CppEditorOutline(this);
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
connect(d->m_cppEditorDocument, &CppEditorDocument::codeWarningsUpdated,
|
|
|
|
|
this, &CppEditorWidget::onCodeWarningsUpdated);
|
|
|
|
|
connect(d->m_cppEditorDocument, &CppEditorDocument::ifdefedOutBlocksUpdated,
|
|
|
|
|
this, &CppEditorWidget::onIfdefedOutBlocksUpdated);
|
|
|
|
|
connect(d->m_cppEditorDocument, &CppEditorDocument::semanticInfoUpdated,
|
2021-08-30 10:58:08 +02:00
|
|
|
this, [this](const SemanticInfo &info) { updateSemanticInfo(info); });
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
connect(d->m_declDefLinkFinder, &FunctionDeclDefLinkFinder::foundLink,
|
|
|
|
|
this, &CppEditorWidget::onFunctionDeclDefLinkFound);
|
|
|
|
|
|
|
|
|
|
connect(&d->m_useSelectionsUpdater,
|
|
|
|
|
&CppUseSelectionsUpdater::selectionsForVariableUnderCursorUpdated,
|
|
|
|
|
&d->m_localRenaming,
|
|
|
|
|
&CppLocalRenaming::updateSelectionsForVariableUnderCursor);
|
|
|
|
|
|
|
|
|
|
connect(&d->m_useSelectionsUpdater, &CppUseSelectionsUpdater::finished, this,
|
2017-09-15 15:16:33 +02:00
|
|
|
[this] (SemanticInfo::LocalUseMap localUses, bool success) {
|
|
|
|
|
if (success) {
|
|
|
|
|
d->m_lastSemanticInfo.localUsesUpdated = true;
|
|
|
|
|
d->m_lastSemanticInfo.localUses = localUses;
|
|
|
|
|
}
|
2017-08-02 14:39:45 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(document(), &QTextDocument::contentsChange,
|
|
|
|
|
&d->m_localRenaming, &CppLocalRenaming::onContentsChangeOfEditorWidgetDocument);
|
|
|
|
|
connect(&d->m_localRenaming, &CppLocalRenaming::finished, [this] {
|
|
|
|
|
cppEditorDocument()->recalculateSemanticInfoDetached();
|
|
|
|
|
});
|
|
|
|
|
connect(&d->m_localRenaming, &CppLocalRenaming::processKeyPressNormally,
|
|
|
|
|
this, &CppEditorWidget::processKeyNormally);
|
2021-07-06 15:50:50 +02:00
|
|
|
connect(this, &QPlainTextEdit::cursorPositionChanged, this, [this] {
|
2022-08-17 13:27:33 +02:00
|
|
|
if (d->m_cppEditorOutline)
|
2021-07-06 15:50:50 +02:00
|
|
|
d->m_cppEditorOutline->updateIndex();
|
|
|
|
|
});
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
connect(cppEditorDocument(), &CppEditorDocument::preprocessorSettingsChanged, this,
|
|
|
|
|
[this](bool customSettings) {
|
|
|
|
|
updateWidgetHighlighting(d->m_preprocessorButton, customSettings);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// set up function declaration - definition link
|
|
|
|
|
d->m_updateFunctionDeclDefLinkTimer.setSingleShot(true);
|
|
|
|
|
d->m_updateFunctionDeclDefLinkTimer.setInterval(UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL);
|
|
|
|
|
connect(&d->m_updateFunctionDeclDefLinkTimer, &QTimer::timeout,
|
|
|
|
|
this, &CppEditorWidget::updateFunctionDeclDefLinkNow);
|
|
|
|
|
connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CppEditorWidget::updateFunctionDeclDefLink);
|
|
|
|
|
connect(this, &QPlainTextEdit::textChanged, this, &CppEditorWidget::updateFunctionDeclDefLink);
|
|
|
|
|
|
|
|
|
|
// set up the use highlighitng
|
2022-07-19 23:36:11 +02:00
|
|
|
connect(this, &CppEditorWidget::cursorPositionChanged, this, [this] {
|
2017-08-02 14:39:45 +02:00
|
|
|
if (!d->m_localRenaming.isActive())
|
|
|
|
|
d->m_useSelectionsUpdater.scheduleUpdate();
|
|
|
|
|
|
|
|
|
|
// Notify selection expander about the changed cursor.
|
|
|
|
|
d->m_cppSelectionChanger.onCursorPositionChanged(textCursor());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Toolbar: Parse context
|
|
|
|
|
ParseContextModel &parseContextModel = cppEditorDocument()->parseContextModel();
|
|
|
|
|
d->m_parseContextWidget = new ParseContextWidget(parseContextModel, this);
|
|
|
|
|
d->m_parseContextAction = insertExtraToolBarWidget(TextEditorWidget::Left,
|
|
|
|
|
d->m_parseContextWidget);
|
|
|
|
|
d->m_parseContextAction->setVisible(false);
|
|
|
|
|
connect(&parseContextModel, &ParseContextModel::updated,
|
|
|
|
|
this, [this](bool areMultipleAvailable) {
|
|
|
|
|
d->m_parseContextAction->setVisible(areMultipleAvailable);
|
|
|
|
|
});
|
2018-04-23 17:03:50 +02:00
|
|
|
|
|
|
|
|
// Toolbar: Outline/Overview combo box
|
2022-08-17 13:27:33 +02:00
|
|
|
setToolbarOutline(d->m_cppEditorOutline->widget());
|
2018-04-23 17:03:50 +02:00
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
// clang-format on
|
|
|
|
|
// Toolbar: '#' Button
|
2022-09-08 15:27:55 +02:00
|
|
|
d->m_preprocessorButton = new QToolButton(this);
|
|
|
|
|
d->m_preprocessorButton->setText(QLatin1String("#"));
|
|
|
|
|
Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG);
|
|
|
|
|
connect(cmd, &Command::keySequenceChanged,
|
|
|
|
|
this, &CppEditorWidget::updatePreprocessorButtonTooltip);
|
|
|
|
|
updatePreprocessorButtonTooltip();
|
|
|
|
|
connect(d->m_preprocessorButton, &QAbstractButton::clicked,
|
|
|
|
|
this, &CppEditorWidget::showPreProcessorWidget);
|
|
|
|
|
insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton);
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2022-08-17 13:27:33 +02:00
|
|
|
connect(this, &TextEditor::TextEditorWidget::toolbarOutlineChanged,
|
|
|
|
|
this, &CppEditorWidget::handleOutlineChanged);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::finalizeInitializationAfterDuplication(TextEditorWidget *other)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(other, return);
|
2019-02-07 11:04:13 +01:00
|
|
|
auto cppEditorWidget = qobject_cast<CppEditorWidget *>(other);
|
2017-08-02 14:39:45 +02:00
|
|
|
QTC_ASSERT(cppEditorWidget, return);
|
|
|
|
|
|
|
|
|
|
if (cppEditorWidget->isSemanticInfoValidExceptLocalUses())
|
|
|
|
|
updateSemanticInfo(cppEditorWidget->semanticInfo());
|
|
|
|
|
const Id selectionKind = CodeWarningsSelection;
|
|
|
|
|
setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind));
|
|
|
|
|
|
|
|
|
|
if (isWidgetHighlighted(cppEditorWidget->d->m_preprocessorButton))
|
|
|
|
|
updateWidgetHighlighting(d->m_preprocessorButton, true);
|
|
|
|
|
|
|
|
|
|
d->m_parseContextWidget->syncToModel();
|
|
|
|
|
d->m_parseContextAction->setVisible(
|
|
|
|
|
d->m_cppEditorDocument->parseContextModel().areMultipleAvailable());
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 16:32:24 +02:00
|
|
|
void CppEditorWidget::setProposals(const TextEditor::IAssistProposal *immediateProposal,
|
|
|
|
|
const TextEditor::IAssistProposal *finalProposal)
|
|
|
|
|
{
|
2021-09-01 18:08:54 +02:00
|
|
|
QTC_ASSERT(isInTestMode(), return);
|
2021-05-27 16:32:24 +02:00
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
emit proposalsReady(immediateProposal, finalProposal);
|
2022-03-30 10:09:37 +02:00
|
|
|
#else
|
|
|
|
|
Q_UNUSED(immediateProposal)
|
|
|
|
|
Q_UNUSED(finalProposal)
|
2021-05-27 16:32:24 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-07 11:04:13 +01:00
|
|
|
CppEditorWidget::~CppEditorWidget() = default;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
CppEditorDocument *CppEditorWidget::cppEditorDocument() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_cppEditorDocument;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::paste()
|
|
|
|
|
{
|
|
|
|
|
if (d->m_localRenaming.handlePaste())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TextEditorWidget::paste();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::cut()
|
|
|
|
|
{
|
|
|
|
|
if (d->m_localRenaming.handleCut())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TextEditorWidget::cut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::selectAll()
|
|
|
|
|
{
|
|
|
|
|
if (d->m_localRenaming.handleSelectAll())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TextEditorWidget::selectAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::onCodeWarningsUpdated(unsigned revision,
|
|
|
|
|
const QList<QTextEdit::ExtraSelection> selections,
|
2019-01-24 06:39:20 +01:00
|
|
|
const RefactorMarkers &refactorMarkers)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
|
|
|
|
if (revision != documentRevision())
|
|
|
|
|
return;
|
|
|
|
|
|
2020-06-17 11:18:35 +02:00
|
|
|
setExtraSelections(TextEditorWidget::CodeWarningsSelection,
|
|
|
|
|
unselectLeadingWhitespace(selections));
|
2019-01-24 06:39:20 +01:00
|
|
|
setRefactorMarkers(refactorMarkers + RefactorMarker::filterOutType(
|
2021-08-30 10:58:08 +02:00
|
|
|
this->refactorMarkers(), Constants::CPP_CLANG_FIXIT_AVAILABLE_MARKER_ID));
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::onIfdefedOutBlocksUpdated(unsigned revision,
|
|
|
|
|
const QList<BlockRange> ifdefedOutBlocks)
|
|
|
|
|
{
|
|
|
|
|
if (revision != documentRevision())
|
|
|
|
|
return;
|
2021-12-03 11:19:59 +01:00
|
|
|
textDocument()->setIfdefedOutBlocks(ifdefedOutBlocks);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-28 07:26:14 +01:00
|
|
|
void CppEditorWidget::findUsages()
|
|
|
|
|
{
|
|
|
|
|
findUsages(textCursor());
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-26 11:35:50 +02:00
|
|
|
void CppEditorWidget::findUsages(QTextCursor cursor)
|
2017-09-19 15:38:20 +02:00
|
|
|
{
|
2018-04-23 12:57:23 +02:00
|
|
|
// 'this' in cursorInEditor is never used (and must never be used) asynchronously.
|
2022-06-02 00:42:28 +02:00
|
|
|
const CursorInEditor cursorInEditor{cursor, textDocument()->filePath(), this, textDocument()};
|
2018-04-23 12:57:23 +02:00
|
|
|
QPointer<CppEditorWidget> cppEditorWidget = this;
|
2022-06-02 00:42:28 +02:00
|
|
|
d->m_modelManager->findUsages(cursorInEditor);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2017-10-26 11:35:50 +02:00
|
|
|
void CppEditorWidget::renameUsages(const QString &replacement, QTextCursor cursor)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2017-10-26 11:35:50 +02:00
|
|
|
if (cursor.isNull())
|
|
|
|
|
cursor = textCursor();
|
2022-06-02 00:42:28 +02:00
|
|
|
CursorInEditor cursorInEditor{cursor, textDocument()->filePath(), this, textDocument()};
|
2018-04-23 12:57:23 +02:00
|
|
|
QPointer<CppEditorWidget> cppEditorWidget = this;
|
2022-06-02 00:23:11 +02:00
|
|
|
d->m_modelManager->globalRename(cursorInEditor, replacement);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::selectBlockUp()
|
|
|
|
|
{
|
|
|
|
|
if (!behaviorSettings().m_smartSelectionChanging)
|
|
|
|
|
return TextEditorWidget::selectBlockUp();
|
|
|
|
|
|
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
|
d->m_cppSelectionChanger.startChangeSelection();
|
|
|
|
|
const bool changed = d->m_cppSelectionChanger
|
|
|
|
|
.changeSelection(CppSelectionChanger::ExpandSelection,
|
|
|
|
|
cursor,
|
|
|
|
|
d->m_lastSemanticInfo.doc);
|
|
|
|
|
if (changed)
|
|
|
|
|
setTextCursor(cursor);
|
|
|
|
|
d->m_cppSelectionChanger.stopChangeSelection();
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::selectBlockDown()
|
|
|
|
|
{
|
|
|
|
|
if (!behaviorSettings().m_smartSelectionChanging)
|
|
|
|
|
return TextEditorWidget::selectBlockDown();
|
|
|
|
|
|
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
|
d->m_cppSelectionChanger.startChangeSelection();
|
|
|
|
|
const bool changed = d->m_cppSelectionChanger
|
|
|
|
|
.changeSelection(CppSelectionChanger::ShrinkSelection,
|
|
|
|
|
cursor,
|
|
|
|
|
d->m_lastSemanticInfo.doc);
|
|
|
|
|
if (changed)
|
|
|
|
|
setTextCursor(cursor);
|
|
|
|
|
d->m_cppSelectionChanger.stopChangeSelection();
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::updateWidgetHighlighting(QWidget *widget, bool highlight)
|
|
|
|
|
{
|
|
|
|
|
if (!widget)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
widget->setProperty("highlightWidget", highlight);
|
|
|
|
|
widget->update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::isWidgetHighlighted(QWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
return widget ? widget->property("highlightWidget").toBool() : false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2021-08-30 10:58:08 +02:00
|
|
|
QList<ProjectPart::ConstPtr> fetchProjectParts(CppModelManager *modelManager,
|
|
|
|
|
const Utils::FilePath &filePath)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2021-08-20 11:21:06 +02:00
|
|
|
QList<ProjectPart::ConstPtr> projectParts = modelManager->projectPart(filePath);
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
if (projectParts.isEmpty())
|
|
|
|
|
projectParts = modelManager->projectPartFromDependencies(filePath);
|
|
|
|
|
if (projectParts.isEmpty())
|
|
|
|
|
projectParts.append(modelManager->fallbackProjectPart());
|
|
|
|
|
|
|
|
|
|
return projectParts;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 11:21:06 +02:00
|
|
|
const ProjectPart *findProjectPartForCurrentProject(
|
|
|
|
|
const QList<ProjectPart::ConstPtr> &projectParts,
|
|
|
|
|
ProjectExplorer::Project *currentProject)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2020-11-18 13:06:55 +01:00
|
|
|
const auto found = std::find_if(projectParts.cbegin(),
|
2017-08-02 14:39:45 +02:00
|
|
|
projectParts.cend(),
|
2021-08-30 10:58:08 +02:00
|
|
|
[&](const ProjectPart::ConstPtr &projectPart) {
|
2021-05-07 16:10:07 +02:00
|
|
|
return projectPart->belongsToProject(currentProject);
|
2017-08-02 14:39:45 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (found != projectParts.cend())
|
|
|
|
|
return (*found).data();
|
|
|
|
|
|
2019-02-07 11:04:13 +01:00
|
|
|
return nullptr;
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2021-08-20 11:21:06 +02:00
|
|
|
const ProjectPart *CppEditorWidget::projectPart() const
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
|
|
|
|
if (!d->m_modelManager)
|
2019-02-07 11:04:13 +01:00
|
|
|
return nullptr;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
auto projectParts = fetchProjectParts(d->m_modelManager, textDocument()->filePath());
|
|
|
|
|
|
|
|
|
|
return findProjectPartForCurrentProject(projectParts,
|
|
|
|
|
ProjectExplorer::ProjectTree::currentProject());
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-17 13:27:33 +02:00
|
|
|
void CppEditorWidget::handleOutlineChanged(const QWidget *newOutline)
|
|
|
|
|
{
|
|
|
|
|
if (d->m_cppEditorOutline && newOutline != d->m_cppEditorOutline->widget()) {
|
|
|
|
|
delete d->m_cppEditorOutline;
|
|
|
|
|
d->m_cppEditorOutline = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (newOutline == nullptr) {
|
|
|
|
|
if (!d->m_cppEditorOutline)
|
|
|
|
|
d->m_cppEditorOutline = new CppEditorOutline(this);
|
|
|
|
|
d->m_cppEditorOutline->updateIndex();
|
|
|
|
|
setToolbarOutline(d->m_cppEditorOutline->widget());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
namespace {
|
|
|
|
|
|
2017-09-21 12:35:24 +02:00
|
|
|
using Utils::Text::selectAt;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
QTextCharFormat occurrencesTextCharFormat()
|
|
|
|
|
{
|
|
|
|
|
using TextEditor::TextEditorSettings;
|
|
|
|
|
|
|
|
|
|
return TextEditorSettings::fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<QTextEdit::ExtraSelection> sourceLocationsToExtraSelections(
|
2022-05-02 12:29:57 +02:00
|
|
|
const Links &sourceLocations,
|
2017-08-02 14:39:45 +02:00
|
|
|
uint selectionLength,
|
|
|
|
|
CppEditorWidget *cppEditorWidget)
|
|
|
|
|
{
|
|
|
|
|
const auto textCharFormat = occurrencesTextCharFormat();
|
|
|
|
|
|
|
|
|
|
QList<QTextEdit::ExtraSelection> selections;
|
|
|
|
|
selections.reserve(int(sourceLocations.size()));
|
|
|
|
|
|
2022-05-02 12:29:57 +02:00
|
|
|
auto sourceLocationToExtraSelection = [&](const Link &sourceLocation) {
|
2017-08-02 14:39:45 +02:00
|
|
|
QTextEdit::ExtraSelection selection;
|
|
|
|
|
|
|
|
|
|
selection.cursor = selectAt(cppEditorWidget->textCursor(),
|
2022-05-02 12:29:57 +02:00
|
|
|
sourceLocation.targetLine,
|
2022-05-11 06:53:25 +02:00
|
|
|
sourceLocation.targetColumn + 1,
|
2017-08-02 14:39:45 +02:00
|
|
|
selectionLength);
|
|
|
|
|
selection.format = textCharFormat;
|
|
|
|
|
|
|
|
|
|
return selection;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::transform(sourceLocations.begin(),
|
|
|
|
|
sourceLocations.end(),
|
|
|
|
|
std::back_inserter(selections),
|
|
|
|
|
sourceLocationToExtraSelection);
|
|
|
|
|
|
|
|
|
|
return selections;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-03 14:13:42 +02:00
|
|
|
void CppEditorWidget::renameSymbolUnderCursor()
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2021-08-20 11:21:06 +02:00
|
|
|
const ProjectPart *projPart = projectPart();
|
2017-09-25 16:41:17 +02:00
|
|
|
if (!projPart)
|
2017-08-03 14:13:42 +02:00
|
|
|
return;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2017-09-26 16:00:30 +02:00
|
|
|
if (d->m_localRenaming.isActive()
|
|
|
|
|
&& d->m_localRenaming.isSameSelection(textCursor().position())) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-08-03 14:13:42 +02:00
|
|
|
d->m_useSelectionsUpdater.abortSchedule();
|
|
|
|
|
|
|
|
|
|
QPointer<CppEditorWidget> cppEditorWidget = this;
|
|
|
|
|
|
2022-05-02 12:29:57 +02:00
|
|
|
auto renameSymbols = [=](const QString &symbolName, const Links &links, int revision) {
|
2017-08-03 14:13:42 +02:00
|
|
|
if (cppEditorWidget) {
|
|
|
|
|
viewport()->setCursor(Qt::IBeamCursor);
|
|
|
|
|
|
|
|
|
|
if (revision != document()->revision())
|
|
|
|
|
return;
|
2022-05-02 12:29:57 +02:00
|
|
|
if (!links.isEmpty()) {
|
2017-08-03 14:13:42 +02:00
|
|
|
QList<QTextEdit::ExtraSelection> selections
|
2022-05-02 12:29:57 +02:00
|
|
|
= sourceLocationsToExtraSelections(links,
|
2017-08-03 14:13:42 +02:00
|
|
|
static_cast<uint>(symbolName.size()),
|
|
|
|
|
cppEditorWidget);
|
|
|
|
|
setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections);
|
2017-09-26 16:00:30 +02:00
|
|
|
d->m_localRenaming.stop();
|
2017-08-03 14:13:42 +02:00
|
|
|
d->m_localRenaming.updateSelectionsForVariableUnderCursor(selections);
|
|
|
|
|
}
|
2017-09-25 10:51:39 +02:00
|
|
|
if (!d->m_localRenaming.start())
|
|
|
|
|
cppEditorWidget->renameUsages();
|
2017-08-03 14:13:42 +02:00
|
|
|
}
|
|
|
|
|
};
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2017-08-03 14:13:42 +02:00
|
|
|
viewport()->setCursor(Qt::BusyCursor);
|
2021-08-30 10:58:08 +02:00
|
|
|
d->m_modelManager->startLocalRenaming(CursorInEditor{textCursor(),
|
|
|
|
|
textDocument()->filePath(),
|
|
|
|
|
this, textDocument()},
|
2017-10-22 22:17:49 +02:00
|
|
|
projPart,
|
|
|
|
|
std::move(renameSymbols));
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::updatePreprocessorButtonTooltip()
|
|
|
|
|
{
|
|
|
|
|
if (!d->m_preprocessorButton)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG);
|
|
|
|
|
QTC_ASSERT(cmd, return );
|
|
|
|
|
d->m_preprocessorButton->setToolTip(cmd->action()->toolTip());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::switchDeclarationDefinition(bool inNextSplit)
|
|
|
|
|
{
|
|
|
|
|
if (!d->m_modelManager)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-05-31 15:57:44 +02:00
|
|
|
const CursorInEditor cursor(textCursor(), textDocument()->filePath(), this, textDocument());
|
|
|
|
|
auto callback = [self = QPointer(this),
|
|
|
|
|
split = inNextSplit != alwaysOpenLinksInNextSplit()](const Link &link) {
|
|
|
|
|
if (self && link.hasValidTarget())
|
|
|
|
|
self->openLink(link, split);
|
|
|
|
|
};
|
2022-04-29 16:52:48 +02:00
|
|
|
CppModelManager::switchDeclDef(cursor, std::move(callback));
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-26 12:59:22 +02:00
|
|
|
void CppEditorWidget::followSymbolToType(bool inNextSplit)
|
|
|
|
|
{
|
|
|
|
|
if (!d->m_modelManager)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const CursorInEditor cursor(textCursor(), textDocument()->filePath(), this, textDocument());
|
|
|
|
|
const auto callback = [self = QPointer(this),
|
|
|
|
|
split = inNextSplit != alwaysOpenLinksInNextSplit()](const Link &link) {
|
|
|
|
|
if (self && link.hasValidTarget())
|
|
|
|
|
self->openLink(link, split);
|
|
|
|
|
};
|
|
|
|
|
CppModelManager::followSymbolToType(cursor, callback, inNextSplit);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-30 18:32:04 +01:00
|
|
|
bool CppEditorWidget::followUrl(const QTextCursor &cursor,
|
2022-09-15 16:35:27 +02:00
|
|
|
const Utils::LinkHandler &processLinkCallback)
|
|
|
|
|
{
|
2022-09-22 11:24:35 +02:00
|
|
|
if (!isSemanticInfoValidExceptLocalUses())
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-09-15 16:35:27 +02:00
|
|
|
const Project * const project = ProjectTree::currentProject();
|
|
|
|
|
if (!project || !project->rootProjectNode())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QList<AST *> astPath = ASTPath(d->m_lastSemanticInfo.doc)(cursor);
|
|
|
|
|
if (astPath.isEmpty())
|
|
|
|
|
return false;
|
|
|
|
|
const StringLiteralAST * const literalAst = astPath.last()->asStringLiteral();
|
|
|
|
|
if (!literalAst)
|
|
|
|
|
return false;
|
|
|
|
|
const StringLiteral * const literal = d->m_lastSemanticInfo.doc->translationUnit()
|
|
|
|
|
->stringLiteral(literalAst->literal_token);
|
|
|
|
|
if (!literal)
|
|
|
|
|
return false;
|
|
|
|
|
const QString theString = QString::fromUtf8(literal->chars(), literal->size());
|
2022-11-30 18:32:04 +01:00
|
|
|
|
|
|
|
|
if (theString.startsWith("https:/") || theString.startsWith("http:/")) {
|
|
|
|
|
Utils::Link link = FilePath::fromPathPart(theString);
|
|
|
|
|
link.linkTextStart = d->m_lastSemanticInfo.doc->translationUnit()->getTokenPositionInDocument(literalAst->literal_token, document());
|
|
|
|
|
link.linkTextEnd = d->m_lastSemanticInfo.doc->translationUnit()->getTokenEndPositionInDocument(literalAst->literal_token, document());
|
|
|
|
|
processLinkCallback(link);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-15 16:35:27 +02:00
|
|
|
if (!theString.startsWith("qrc:/") && !theString.startsWith(":/"))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const Node * const nodeForPath = project->rootProjectNode()->findNode(
|
|
|
|
|
[qrcPath = theString.mid(theString.indexOf(':') + 1)](Node *n) {
|
|
|
|
|
if (!n->asFileNode())
|
|
|
|
|
return false;
|
|
|
|
|
const auto qrcNode = dynamic_cast<ResourceFileNode *>(n);
|
|
|
|
|
return qrcNode && qrcNode->qrcPath() == qrcPath;
|
|
|
|
|
});
|
|
|
|
|
if (!nodeForPath)
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-11-30 18:40:05 +01:00
|
|
|
Link link(nodeForPath->filePath());
|
|
|
|
|
link.linkTextStart = d->m_lastSemanticInfo.doc->translationUnit()->getTokenPositionInDocument(literalAst->literal_token, document());
|
|
|
|
|
link.linkTextEnd = d->m_lastSemanticInfo.doc->translationUnit()->getTokenEndPositionInDocument(literalAst->literal_token, document());
|
|
|
|
|
processLinkCallback(link);
|
2022-09-15 16:35:27 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-21 11:58:16 +01:00
|
|
|
void CppEditorWidget::findLinkAt(const QTextCursor &cursor,
|
2022-06-03 15:17:33 +02:00
|
|
|
const LinkHandler &processLinkCallback,
|
2018-02-21 11:58:16 +01:00
|
|
|
bool resolveTarget,
|
|
|
|
|
bool inNextSplit)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
|
|
|
|
if (!d->m_modelManager)
|
2018-02-21 11:58:16 +01:00
|
|
|
return processLinkCallback(Utils::Link());
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2022-11-30 18:32:04 +01:00
|
|
|
if (followUrl(cursor, processLinkCallback))
|
2022-09-15 16:35:27 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-05-28 13:49:26 +02:00
|
|
|
const Utils::FilePath &filePath = textDocument()->filePath();
|
2017-08-03 16:43:38 +02:00
|
|
|
|
2021-10-26 17:49:54 +02:00
|
|
|
// Let following a "leaf" C++ symbol take us to the designer, if we are in a generated
|
|
|
|
|
// UI header.
|
|
|
|
|
QTextCursor c(cursor);
|
|
|
|
|
c.select(QTextCursor::WordUnderCursor);
|
2022-06-03 14:58:37 +02:00
|
|
|
LinkHandler callbackWrapper = [start = c.selectionStart(), end = c.selectionEnd(),
|
2022-06-03 15:17:33 +02:00
|
|
|
doc = QPointer(cursor.document()), callback = processLinkCallback,
|
2021-10-26 17:49:54 +02:00
|
|
|
filePath](const Link &link) {
|
|
|
|
|
const int linkPos = doc ? Text::positionInText(doc, link.targetLine, link.targetColumn + 1)
|
|
|
|
|
: -1;
|
|
|
|
|
if (link.targetFilePath == filePath && linkPos >= start && linkPos < end) {
|
|
|
|
|
const QString fileName = filePath.fileName();
|
|
|
|
|
if (fileName.startsWith("ui_") && fileName.endsWith(".h")) {
|
|
|
|
|
const QString uiFileName = fileName.mid(3, fileName.length() - 4) + "ui";
|
|
|
|
|
for (const Project * const project : SessionManager::projects()) {
|
|
|
|
|
const auto nodeMatcher = [uiFileName](Node *n) {
|
|
|
|
|
return n->filePath().fileName() == uiFileName;
|
|
|
|
|
};
|
|
|
|
|
if (const Node * const uiNode = project->rootProjectNode()
|
|
|
|
|
->findNode(nodeMatcher)) {
|
|
|
|
|
EditorManager::openEditor(uiNode->filePath());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
callback(link);
|
|
|
|
|
};
|
2022-04-29 16:52:48 +02:00
|
|
|
CppModelManager::followSymbol(
|
2021-08-30 10:58:08 +02:00
|
|
|
CursorInEditor{cursor, filePath, this, textDocument()},
|
2022-06-03 15:17:33 +02:00
|
|
|
callbackWrapper,
|
2021-05-19 11:12:07 +02:00
|
|
|
resolveTarget,
|
|
|
|
|
inNextSplit);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned CppEditorWidget::documentRevision() const
|
|
|
|
|
{
|
|
|
|
|
return document()->revision();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::isSemanticInfoValidExceptLocalUses() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_lastSemanticInfo.doc && d->m_lastSemanticInfo.revision == documentRevision()
|
|
|
|
|
&& !d->m_lastSemanticInfo.snapshot.isEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::isSemanticInfoValid() const
|
|
|
|
|
{
|
|
|
|
|
return isSemanticInfoValidExceptLocalUses() && d->m_lastSemanticInfo.localUsesUpdated;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-14 16:13:29 +01:00
|
|
|
bool CppEditorWidget::isRenaming() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_localRenaming.isActive();
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
SemanticInfo CppEditorWidget::semanticInfo() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_lastSemanticInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::event(QEvent *e)
|
|
|
|
|
{
|
|
|
|
|
switch (e->type()) {
|
|
|
|
|
case QEvent::ShortcutOverride:
|
|
|
|
|
// handle escape manually if a rename is active
|
|
|
|
|
if (static_cast<QKeyEvent *>(e)->key() == Qt::Key_Escape && d->m_localRenaming.isActive()) {
|
|
|
|
|
e->accept();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TextEditorWidget::event(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::processKeyNormally(QKeyEvent *e)
|
|
|
|
|
{
|
|
|
|
|
TextEditorWidget::keyPressEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 14:19:06 +01:00
|
|
|
static void addRefactoringActions(QMenu *menu, std::unique_ptr<AssistInterface> iface)
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2017-09-14 17:29:34 +02:00
|
|
|
if (!iface || !menu)
|
|
|
|
|
return;
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2017-09-14 17:29:34 +02:00
|
|
|
using Processor = QScopedPointer<IAssistProcessor>;
|
|
|
|
|
using Proposal = QScopedPointer<IAssistProposal>;
|
|
|
|
|
|
2021-09-15 07:04:12 +02:00
|
|
|
const Processor processor(
|
2022-11-15 14:19:06 +01:00
|
|
|
CppEditorPlugin::instance()->quickFixProvider()->createProcessor(iface.get()));
|
|
|
|
|
const Proposal proposal(processor->start(std::move(iface)));
|
2017-09-14 17:29:34 +02:00
|
|
|
if (proposal) {
|
2018-02-14 14:32:51 +01:00
|
|
|
auto model = proposal->model().staticCast<GenericProposalModel>();
|
2017-09-14 17:29:34 +02:00
|
|
|
for (int index = 0; index < model->size(); ++index) {
|
|
|
|
|
const auto item = static_cast<AssistProposalItem *>(model->proposalItem(index));
|
|
|
|
|
const QuickFixOperation::Ptr op = item->data().value<QuickFixOperation::Ptr>();
|
|
|
|
|
const QAction *action = menu->addAction(op->description());
|
|
|
|
|
QObject::connect(action, &QAction::triggered, menu, [op] { op->perform(); });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2017-09-15 15:16:33 +02:00
|
|
|
class ProgressIndicatorMenuItem : public QWidgetAction
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ProgressIndicatorMenuItem(QObject *parent) : QWidgetAction(parent) {}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
QWidget *createWidget(QWidget *parent = nullptr) override
|
|
|
|
|
{
|
|
|
|
|
return new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small, parent);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-09-14 17:29:34 +02:00
|
|
|
QMenu *CppEditorWidget::createRefactorMenu(QWidget *parent) const
|
|
|
|
|
{
|
2023-01-11 20:43:10 +01:00
|
|
|
auto *menu = new QMenu(Tr::tr("&Refactor"), parent);
|
2020-05-06 07:30:33 +02:00
|
|
|
menu->addAction(ActionManager::command(TextEditor::Constants::RENAME_SYMBOL)->action());
|
2017-08-02 14:39:45 +02:00
|
|
|
|
2017-09-14 17:29:34 +02:00
|
|
|
// ### enable
|
|
|
|
|
// updateSemanticInfo(m_semanticHighlighter->semanticInfo(currentSource()));
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
if (isSemanticInfoValidExceptLocalUses()) {
|
2017-09-15 15:16:33 +02:00
|
|
|
d->m_useSelectionsUpdater.abortSchedule();
|
|
|
|
|
|
|
|
|
|
const CppUseSelectionsUpdater::RunnerInfo runnerInfo = d->m_useSelectionsUpdater.update();
|
|
|
|
|
switch (runnerInfo) {
|
|
|
|
|
case CppUseSelectionsUpdater::RunnerInfo::AlreadyUpToDate:
|
|
|
|
|
addRefactoringActions(menu, createAssistInterface(QuickFix, ExplicitlyInvoked));
|
|
|
|
|
break;
|
|
|
|
|
case CppUseSelectionsUpdater::RunnerInfo::Started: {
|
|
|
|
|
// Update the refactor menu once we get the results.
|
|
|
|
|
auto *progressIndicatorMenuItem = new ProgressIndicatorMenuItem(menu);
|
|
|
|
|
menu->addAction(progressIndicatorMenuItem);
|
|
|
|
|
|
|
|
|
|
connect(&d->m_useSelectionsUpdater, &CppUseSelectionsUpdater::finished,
|
|
|
|
|
menu, [=] (SemanticInfo::LocalUseMap, bool success) {
|
|
|
|
|
QTC_CHECK(success);
|
|
|
|
|
menu->removeAction(progressIndicatorMenuItem);
|
|
|
|
|
addRefactoringActions(menu, createAssistInterface(QuickFix, ExplicitlyInvoked));
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case CppUseSelectionsUpdater::RunnerInfo::FailedToStart:
|
|
|
|
|
case CppUseSelectionsUpdater::RunnerInfo::Invalid:
|
|
|
|
|
QTC_CHECK(false && "Unexpected CppUseSelectionsUpdater runner result");
|
|
|
|
|
}
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-14 17:29:34 +02:00
|
|
|
return menu;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void appendCustomContextMenuActionsAndMenus(QMenu *menu, QMenu *refactorMenu)
|
|
|
|
|
{
|
|
|
|
|
bool isRefactoringMenuAdded = false;
|
|
|
|
|
const QMenu *contextMenu = ActionManager::actionContainer(Constants::M_CONTEXT)->menu();
|
|
|
|
|
for (QAction *action : contextMenu->actions()) {
|
2017-08-02 14:39:45 +02:00
|
|
|
menu->addAction(action);
|
2021-08-30 10:58:08 +02:00
|
|
|
if (action->objectName() == QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT)) {
|
2017-09-14 17:29:34 +02:00
|
|
|
isRefactoringMenuAdded = true;
|
|
|
|
|
menu->addMenu(refactorMenu);
|
|
|
|
|
}
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-14 17:29:34 +02:00
|
|
|
QTC_CHECK(isRefactoringMenuAdded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::contextMenuEvent(QContextMenuEvent *e)
|
|
|
|
|
{
|
|
|
|
|
const QPointer<QMenu> menu(new QMenu(this));
|
|
|
|
|
|
|
|
|
|
appendCustomContextMenuActionsAndMenus(menu, createRefactorMenu(menu));
|
2017-08-02 14:39:45 +02:00
|
|
|
appendStandardContextMenuActions(menu);
|
|
|
|
|
|
|
|
|
|
menu->exec(e->globalPos());
|
2017-09-14 17:29:34 +02:00
|
|
|
if (menu)
|
|
|
|
|
delete menu; // OK, menu was not already deleted by closed editor widget.
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::keyPressEvent(QKeyEvent *e)
|
|
|
|
|
{
|
|
|
|
|
if (d->m_localRenaming.handleKeyPressEvent(e))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (handleStringSplitting(e))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
|
|
|
|
if (trySplitComment(this, semanticInfo().snapshot)) {
|
|
|
|
|
e->accept();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextEditorWidget::keyPressEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CppEditorWidget::handleStringSplitting(QKeyEvent *e) const
|
|
|
|
|
{
|
|
|
|
|
if (!TextEditorSettings::completionSettings().m_autoSplitStrings)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
|
|
|
|
|
|
const Kind stringKind = CPlusPlus::MatchingText::stringKindAtCursor(cursor);
|
|
|
|
|
if (stringKind >= T_FIRST_STRING_LITERAL && stringKind < T_FIRST_RAW_STRING_LITERAL) {
|
|
|
|
|
cursor.beginEditBlock();
|
|
|
|
|
if (cursor.positionInBlock() > 0
|
|
|
|
|
&& cursor.block().text().at(cursor.positionInBlock() - 1) == QLatin1Char('\\')) {
|
|
|
|
|
// Already escaped: simply go back to line, but do not indent.
|
|
|
|
|
cursor.insertText(QLatin1String("\n"));
|
|
|
|
|
} else if (e->modifiers() & Qt::ShiftModifier) {
|
|
|
|
|
// With 'shift' modifier, escape the end of line character
|
|
|
|
|
// and start at beginning of next line.
|
|
|
|
|
cursor.insertText(QLatin1String("\\\n"));
|
|
|
|
|
} else {
|
|
|
|
|
// End the current string, and start a new one on the line, properly indented.
|
|
|
|
|
cursor.insertText(QLatin1String("\"\n\""));
|
|
|
|
|
textDocument()->autoIndent(cursor);
|
|
|
|
|
}
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
e->accept();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::slotCodeStyleSettingsChanged(const QVariant &)
|
|
|
|
|
{
|
|
|
|
|
QtStyleCodeFormatter formatter;
|
|
|
|
|
formatter.invalidateCache(document());
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-03 14:13:42 +02:00
|
|
|
void CppEditorWidget::updateSemanticInfo()
|
|
|
|
|
{
|
|
|
|
|
updateSemanticInfo(d->m_cppEditorDocument->recalculateSemanticInfo(),
|
|
|
|
|
/*updateUseSelectionSynchronously=*/ true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
void CppEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo,
|
|
|
|
|
bool updateUseSelectionSynchronously)
|
|
|
|
|
{
|
|
|
|
|
if (semanticInfo.revision != documentRevision())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
d->m_lastSemanticInfo = semanticInfo;
|
|
|
|
|
|
|
|
|
|
if (!d->m_localRenaming.isActive()) {
|
|
|
|
|
const CppUseSelectionsUpdater::CallType type = updateUseSelectionSynchronously
|
2017-09-15 15:25:27 +02:00
|
|
|
? CppUseSelectionsUpdater::CallType::Synchronous
|
|
|
|
|
: CppUseSelectionsUpdater::CallType::Asynchronous;
|
2017-08-02 14:39:45 +02:00
|
|
|
d->m_useSelectionsUpdater.update(type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// schedule a check for a decl/def link
|
|
|
|
|
updateFunctionDeclDefLink();
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-20 12:57:23 +02:00
|
|
|
bool CppEditorWidget::isOldStyleSignalOrSlot() const
|
|
|
|
|
{
|
|
|
|
|
QTextCursor tc(textCursor());
|
|
|
|
|
const QString content = textDocument()->plainText();
|
|
|
|
|
|
|
|
|
|
return CppEditor::CppModelManager::instance()
|
2022-08-10 11:13:32 +02:00
|
|
|
->getSignalSlotType(textDocument()->filePath(), content.toUtf8(), tc.position())
|
2022-06-20 12:57:23 +02:00
|
|
|
== CppEditor::SignalSlotType::OldStyleSignal;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 14:19:06 +01:00
|
|
|
std::unique_ptr<AssistInterface> CppEditorWidget::createAssistInterface(AssistKind kind,
|
|
|
|
|
AssistReason reason) const
|
2017-08-02 14:39:45 +02:00
|
|
|
{
|
2020-07-31 16:50:03 +02:00
|
|
|
if (kind == Completion || kind == FunctionHint) {
|
|
|
|
|
CppCompletionAssistProvider * const cap = kind == Completion
|
2021-06-18 16:30:03 +02:00
|
|
|
? qobject_cast<CppCompletionAssistProvider *>(cppEditorDocument()->completionAssistProvider())
|
|
|
|
|
: qobject_cast<CppCompletionAssistProvider *>(cppEditorDocument()->functionHintAssistProvider());
|
2022-06-20 12:57:23 +02:00
|
|
|
|
2022-07-19 23:36:11 +02:00
|
|
|
auto getFeatures = [this] {
|
2017-08-02 14:39:45 +02:00
|
|
|
LanguageFeatures features = LanguageFeatures::defaultFeatures();
|
|
|
|
|
if (Document::Ptr doc = d->m_lastSemanticInfo.doc)
|
|
|
|
|
features = doc->languageFeatures();
|
|
|
|
|
features.objCEnabled |= cppEditorDocument()->isObjCEnabled();
|
2022-06-20 12:57:23 +02:00
|
|
|
return features;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (cap)
|
2022-11-15 14:19:06 +01:00
|
|
|
return cap->createAssistInterface(textDocument()->filePath(), this, getFeatures(), reason);
|
2022-06-20 12:57:23 +02:00
|
|
|
else {
|
|
|
|
|
if (isOldStyleSignalOrSlot())
|
|
|
|
|
return CppModelManager::instance()
|
|
|
|
|
->completionAssistProvider()
|
|
|
|
|
->createAssistInterface(textDocument()->filePath(), this, getFeatures(), reason);
|
2021-06-18 16:30:03 +02:00
|
|
|
return TextEditorWidget::createAssistInterface(kind, reason);
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
} else if (kind == QuickFix) {
|
|
|
|
|
if (isSemanticInfoValid())
|
2022-11-15 14:19:06 +01:00
|
|
|
return std::make_unique<CppQuickFixInterface>(const_cast<CppEditorWidget *>(this), reason);
|
2017-08-02 14:39:45 +02:00
|
|
|
} else {
|
|
|
|
|
return TextEditorWidget::createAssistInterface(kind, reason);
|
|
|
|
|
}
|
2019-02-07 11:04:13 +01:00
|
|
|
return nullptr;
|
2017-08-02 14:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSharedPointer<FunctionDeclDefLink> CppEditorWidget::declDefLink() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_declDefLink;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::updateFunctionDeclDefLink()
|
|
|
|
|
{
|
|
|
|
|
const int pos = textCursor().selectionStart();
|
|
|
|
|
|
|
|
|
|
// if there's already a link, abort it if the cursor is outside or the name changed
|
|
|
|
|
// (adding a prefix is an exception since the user might type a return type)
|
|
|
|
|
if (d->m_declDefLink
|
|
|
|
|
&& (pos < d->m_declDefLink->linkSelection.selectionStart()
|
|
|
|
|
|| pos > d->m_declDefLink->linkSelection.selectionEnd()
|
|
|
|
|
|| !d->m_declDefLink->nameSelection.selectedText().trimmed().endsWith(
|
|
|
|
|
d->m_declDefLink->nameInitial))) {
|
|
|
|
|
abortDeclDefLink();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't start a new scan if there's one active and the cursor is already in the scanned area
|
|
|
|
|
const QTextCursor scannedSelection = d->m_declDefLinkFinder->scannedSelection();
|
|
|
|
|
if (!scannedSelection.isNull() && scannedSelection.selectionStart() <= pos
|
|
|
|
|
&& scannedSelection.selectionEnd() >= pos) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d->m_updateFunctionDeclDefLinkTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::updateFunctionDeclDefLinkNow()
|
|
|
|
|
{
|
|
|
|
|
IEditor *editor = EditorManager::currentEditor();
|
|
|
|
|
if (!editor || editor->widget() != this)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const Snapshot semanticSnapshot = d->m_lastSemanticInfo.snapshot;
|
|
|
|
|
const Document::Ptr semanticDoc = d->m_lastSemanticInfo.doc;
|
|
|
|
|
|
|
|
|
|
if (d->m_declDefLink) {
|
|
|
|
|
// update the change marker
|
|
|
|
|
const Utils::ChangeSet changes = d->m_declDefLink->changes(semanticSnapshot);
|
|
|
|
|
if (changes.isEmpty())
|
|
|
|
|
d->m_declDefLink->hideMarker(this);
|
|
|
|
|
else
|
|
|
|
|
d->m_declDefLink->showMarker(this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isSemanticInfoValidExceptLocalUses())
|
|
|
|
|
return;
|
|
|
|
|
|
2018-12-16 15:31:28 +01:00
|
|
|
Snapshot snapshot = d->m_modelManager->snapshot();
|
2017-08-02 14:39:45 +02:00
|
|
|
snapshot.insert(semanticDoc);
|
|
|
|
|
|
|
|
|
|
d->m_declDefLinkFinder->startFindLinkAt(textCursor(), semanticDoc, snapshot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink> link)
|
|
|
|
|
{
|
|
|
|
|
abortDeclDefLink();
|
|
|
|
|
d->m_declDefLink = link;
|
|
|
|
|
IDocument *targetDocument = DocumentModel::documentForFilePath(
|
2021-05-28 12:02:36 +02:00
|
|
|
d->m_declDefLink->targetFile->filePath());
|
2017-08-02 14:39:45 +02:00
|
|
|
if (textDocument() != targetDocument) {
|
|
|
|
|
if (auto textDocument = qobject_cast<BaseTextDocument *>(targetDocument))
|
|
|
|
|
connect(textDocument,
|
|
|
|
|
&IDocument::contentsChanged,
|
|
|
|
|
this,
|
|
|
|
|
&CppEditorWidget::abortDeclDefLink);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::applyDeclDefLinkChanges(bool jumpToMatch)
|
|
|
|
|
{
|
|
|
|
|
if (!d->m_declDefLink)
|
|
|
|
|
return;
|
|
|
|
|
d->m_declDefLink->apply(this, jumpToMatch);
|
|
|
|
|
abortDeclDefLink();
|
|
|
|
|
updateFunctionDeclDefLink();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::encourageApply()
|
|
|
|
|
{
|
|
|
|
|
if (d->m_localRenaming.encourageApply())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
TextEditorWidget::encourageApply();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::abortDeclDefLink()
|
|
|
|
|
{
|
|
|
|
|
if (!d->m_declDefLink)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IDocument *targetDocument = DocumentModel::documentForFilePath(
|
2021-05-28 12:02:36 +02:00
|
|
|
d->m_declDefLink->targetFile->filePath());
|
2017-08-02 14:39:45 +02:00
|
|
|
if (textDocument() != targetDocument) {
|
|
|
|
|
if (auto textDocument = qobject_cast<BaseTextDocument *>(targetDocument))
|
|
|
|
|
disconnect(textDocument,
|
|
|
|
|
&IDocument::contentsChanged,
|
|
|
|
|
this,
|
|
|
|
|
&CppEditorWidget::abortDeclDefLink);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d->m_declDefLink->hideMarker(this);
|
|
|
|
|
d->m_declDefLink.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CppEditorWidget::showPreProcessorWidget()
|
|
|
|
|
{
|
2022-09-30 10:32:18 +02:00
|
|
|
const FilePath filePath = textDocument()->filePath();
|
2017-08-02 14:39:45 +02:00
|
|
|
|
|
|
|
|
CppPreProcessorDialog dialog(filePath, this);
|
|
|
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
|
|
|
const QByteArray extraDirectives = dialog.extraPreprocessorDirectives().toUtf8();
|
|
|
|
|
cppEditorDocument()->setExtraPreprocessorDirectives(extraDirectives);
|
|
|
|
|
cppEditorDocument()->scheduleProcessDocument();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-18 10:19:27 +02:00
|
|
|
void CppEditorWidget::invokeTextEditorWidgetAssist(TextEditor::AssistKind assistKind,
|
|
|
|
|
TextEditor::IAssistProvider *provider)
|
|
|
|
|
{
|
|
|
|
|
invokeAssist(assistKind, provider);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-17 11:18:35 +02:00
|
|
|
const QList<QTextEdit::ExtraSelection> CppEditorWidget::unselectLeadingWhitespace(
|
|
|
|
|
const QList<QTextEdit::ExtraSelection> &selections)
|
|
|
|
|
{
|
|
|
|
|
QList<QTextEdit::ExtraSelection> filtered;
|
|
|
|
|
for (const QTextEdit::ExtraSelection &sel : selections) {
|
|
|
|
|
QList<QTextEdit::ExtraSelection> splitSelections;
|
|
|
|
|
int firstNonWhitespacePos = -1;
|
|
|
|
|
int lastNonWhitespacePos = -1;
|
|
|
|
|
bool split = false;
|
|
|
|
|
const QTextBlock firstBlock = sel.cursor.document()->findBlock(sel.cursor.selectionStart());
|
|
|
|
|
bool inIndentation = firstBlock.position() == sel.cursor.selectionStart();
|
|
|
|
|
const auto createSplitSelection = [&] {
|
|
|
|
|
QTextEdit::ExtraSelection newSelection;
|
|
|
|
|
newSelection.cursor = QTextCursor(sel.cursor.document());
|
|
|
|
|
newSelection.cursor.setPosition(firstNonWhitespacePos);
|
|
|
|
|
newSelection.cursor.setPosition(lastNonWhitespacePos + 1, QTextCursor::KeepAnchor);
|
|
|
|
|
newSelection.format = sel.format;
|
|
|
|
|
splitSelections << newSelection;
|
|
|
|
|
};
|
|
|
|
|
for (int i = sel.cursor.selectionStart(); i < sel.cursor.selectionEnd(); ++i) {
|
|
|
|
|
const QChar curChar = sel.cursor.document()->characterAt(i);
|
|
|
|
|
if (!curChar.isSpace()) {
|
|
|
|
|
if (firstNonWhitespacePos == -1)
|
|
|
|
|
firstNonWhitespacePos = i;
|
|
|
|
|
lastNonWhitespacePos = i;
|
|
|
|
|
}
|
|
|
|
|
if (!inIndentation) {
|
|
|
|
|
if (curChar == QChar::ParagraphSeparator)
|
|
|
|
|
inIndentation = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (curChar == QChar::ParagraphSeparator)
|
|
|
|
|
continue;
|
|
|
|
|
if (curChar.isSpace()) {
|
|
|
|
|
if (firstNonWhitespacePos != -1) {
|
|
|
|
|
createSplitSelection();
|
|
|
|
|
firstNonWhitespacePos = -1;
|
|
|
|
|
lastNonWhitespacePos = -1;
|
|
|
|
|
}
|
|
|
|
|
split = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
inIndentation = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!split) {
|
|
|
|
|
filtered << sel;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (firstNonWhitespacePos != -1)
|
|
|
|
|
createSplitSelection();
|
|
|
|
|
filtered << splitSelections;
|
|
|
|
|
}
|
|
|
|
|
return filtered;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 18:08:54 +02:00
|
|
|
bool CppEditorWidget::isInTestMode() const { return d->inTestMode; }
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
void CppEditorWidget::enableTestMode() { d->inTestMode = true; }
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-08-02 14:39:45 +02:00
|
|
|
} // namespace CppEditor
|