aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/clangcodemodel
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/clangcodemodel')
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp2
-rw-r--r--src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp37
-rw-r--r--src/plugins/clangcodemodel/clangdclient.cpp277
-rw-r--r--src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp24
-rw-r--r--src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h3
-rw-r--r--src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp3
-rw-r--r--src/plugins/clangcodemodel/clangmodelmanagersupport.cpp8
-rw-r--r--src/plugins/clangcodemodel/clangtextmark.cpp4
-rw-r--r--src/plugins/clangcodemodel/clangutils.cpp13
-rw-r--r--src/plugins/clangcodemodel/clangutils.h4
10 files changed, 241 insertions, 134 deletions
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index a6ead8c3fe..3a13c4542d 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -82,7 +82,7 @@ void ClangCodeModelPlugin::generateCompilationDB()
QFuture<GenerateCompilationDbResult> task
= QtConcurrent::run(&Internal::generateCompilationDB, projectInfo,
- CompilationDbPurpose::Project,
+ projectInfo->buildRoot(), CompilationDbPurpose::Project,
warningsConfigForProject(target->project()),
optionsForProject(target->project()));
Core::ProgressManager::addTask(task, tr("Generating Compilation DB"), "generate compilation db");
diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp
index af964f5be9..1d924f8b2d 100644
--- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp
+++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp
@@ -36,6 +36,7 @@
#include <cppeditor/cppdoxygen.h>
#include <cppeditor/cppmodelmanager.h>
+#include <cppeditor/cpptoolsreuse.h>
#include <cppeditor/editordocumenthandle.h>
#include <texteditor/codeassist/assistproposalitem.h>
@@ -44,10 +45,7 @@
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
#include <texteditor/texteditorsettings.h>
-#include <cplusplus/BackwardsScanner.h>
-#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Icons.h>
-#include <cplusplus/SimpleLexer.h>
#include <clangsupport/filecontainer.h>
@@ -431,37 +429,8 @@ bool ClangCompletionAssistProcessor::accepts() const
if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
const QChar firstCharacter = m_interface->characterAt(startOfName);
if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) {
- // Finally check that we're not inside a comment or string (code copied from startOfOperator)
- QTextCursor tc(m_interface->textDocument());
- tc.setPosition(pos);
-
- SimpleLexer tokenize;
- LanguageFeatures lf = tokenize.languageFeatures();
- lf.qtMocRunEnabled = true;
- lf.objCEnabled = true;
- tokenize.setLanguageFeatures(lf);
- tokenize.setSkipComments(false);
- const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
- const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
- const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
-
- if (!tk.isComment() && !tk.isLiteral()) {
- return true;
- } else if (tk.isLiteral()
- && tokens.size() == 3
- && tokens.at(0).kind() == T_POUND
- && tokens.at(1).kind() == T_IDENTIFIER) {
- const QString &line = tc.block().text();
- const Token &idToken = tokens.at(1);
- QStringView identifier = Utils::midView(line,
- idToken.utf16charsBegin(),
- idToken.utf16chars());
- if (identifier == QLatin1String("include")
- || identifier == QLatin1String("include_next")
- || (m_interface->objcEnabled() && identifier == QLatin1String("import"))) {
- return true;
- }
- }
+ return !CppEditor::isInCommentOrString(m_interface.data(),
+ m_interface->languageFeatures());
}
}
}
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp
index 5b3e76bee8..2b6044f3ac 100644
--- a/src/plugins/clangcodemodel/clangdclient.cpp
+++ b/src/plugins/clangcodemodel/clangdclient.cpp
@@ -94,8 +94,22 @@ static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", Q
static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg);
static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
static Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg);
+static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion",
+ QtWarningMsg);
static QString indexingToken() { return "backgroundIndexProgress"; }
+static QStringView subViewLen(const QString &s, qsizetype start, qsizetype length)
+{
+ if (start < 0 || length < 0 || start + length > s.length())
+ return {};
+ return QStringView(s).mid(start, length);
+}
+
+static QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end)
+{
+ return subViewLen(s, start, end - start);
+}
+
class AstNode : public JsonObject
{
public:
@@ -277,6 +291,58 @@ public:
- openingQuoteOffset - 1);
}
+ enum class FileStatus { Ours, Foreign, Mixed, Unknown };
+ FileStatus fileStatus(const Utils::FilePath &thisFile) const
+ {
+ const Utils::optional<QString> arcanaString = arcana();
+ if (!arcanaString)
+ return FileStatus::Unknown;
+
+ // Example arcanas:
+ // "FunctionDecl 0x7fffb5d0dbd0 </tmp/test.cpp:1:1, line:5:1> line:1:6 func 'void ()'"
+ // "VarDecl 0x7fffb5d0dcf0 </tmp/test.cpp:2:5, /tmp/test.h:1:1> /tmp/test.cpp:2:10 b 'bool' cinit"
+ // The second one is for a particularly silly construction where the RHS of an
+ // initialization comes from an included header.
+ const int openPos = arcanaString->indexOf('<');
+ if (openPos == -1)
+ return FileStatus::Unknown;
+ const int closePos = arcanaString->indexOf('>', openPos + 1);
+ if (closePos == -1)
+ return FileStatus::Unknown;
+ bool hasOurs = false;
+ bool hasOther = false;
+ for (int startPos = openPos + 1; startPos < closePos;) {
+ int colon1Pos = arcanaString->indexOf(':', startPos);
+ if (colon1Pos == -1 || colon1Pos > closePos)
+ break;
+ if (Utils::HostOsInfo::isWindowsHost())
+ colon1Pos = arcanaString->indexOf(':', colon1Pos + 1);
+ if (colon1Pos == -1 || colon1Pos > closePos)
+ break;
+ const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2);
+ if (colon2Pos == -1 || colon2Pos > closePos)
+ break;
+ const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toString().toInt(); // TODO: Drop toString() once we require >= Qt 5.15
+ if (line == 0)
+ break;
+ const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos);
+ if (fileOrLineString != QLatin1String("line")) {
+ if (Utils::FilePath::fromUserInput(fileOrLineString.toString()) == thisFile)
+ hasOurs = true;
+ else
+ hasOther = true;
+ }
+ const int commaPos = arcanaString->indexOf(',', colon2Pos + 2);
+ if (commaPos != -1)
+ startPos = commaPos + 2;
+ else
+ break;
+ }
+ if (hasOurs)
+ return hasOther ? FileStatus::Mixed : FileStatus::Ours;
+ return hasOther ? FileStatus::Foreign : FileStatus::Unknown;
+ }
+
// For debugging.
void print(int indent = 0) const
{
@@ -298,6 +364,7 @@ static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
QList<AstNode> path;
QList<AstNode> queue{root};
bool isRoot = true;
+
while (!queue.isEmpty()) {
AstNode curNode = queue.takeFirst();
if (!isRoot && !curNode.hasRange())
@@ -309,13 +376,39 @@ static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
const auto children = curNode.children();
if (!children)
break;
- queue = children.value();
+ if (curNode.kind() == "Function" || curNode.role() == "expression") {
+ // Functions and expressions can contain implicit nodes that make the list unsorted.
+ // They cannot be ignored, as we need to consider them in certain contexts.
+ // Therefore, the binary search cannot be used here.
+ queue = *children;
+ } else {
+ queue.clear();
+
+ // Class and struct nodes can contain implicit constructors, destructors and
+ // operators, which appear at the end of the list, but whose range is the same
+ // as the class name. Therefore, we must force them not to compare less to
+ // anything else.
+ static const auto leftOfRange = [](const AstNode &node, const Range &range) {
+ return node.range().isLeftOf(range) && !node.arcanaContains(" implicit ");
+ };
+
+ for (auto it = std::lower_bound(children->cbegin(), children->cend(), range,
+ leftOfRange);
+ it != children->cend() && !range.isLeftOf(it->range()); ++it) {
+ queue << *it;
+ }
+ }
}
isRoot = false;
}
return path;
}
+static QList<AstNode> getAstPath(const AstNode &root, const Position &pos)
+{
+ return getAstPath(root, Range(pos, pos));
+}
+
static Usage::Type getUsageType(const QList<AstNode> &path)
{
bool potentialWrite = false;
@@ -685,20 +778,12 @@ private:
case CustomAssistMode::Preprocessor:
static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro);
for (const QString &completion
- : CppEditor::CppCompletionAssistProcessor::preprocessorCompletions())
+ : CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) {
completions << createItem(completion, macroIcon);
- const CppEditor::ProjectFile::Kind fileType
- = CppEditor::ProjectFile::classify(interface->filePath().toString());
- switch (fileType) {
- case CppEditor::ProjectFile::ObjCHeader:
- case CppEditor::ProjectFile::ObjCXXHeader:
- case CppEditor::ProjectFile::ObjCSource:
- case CppEditor::ProjectFile::ObjCXXSource:
- completions << createItem("import", macroIcon);
- break;
- default:
- break;
}
+ if (CppEditor::ProjectFile::isObjC(interface->filePath().toString()))
+ completions << createItem("import", macroIcon);
+ break;
}
GenericProposalModelPtr model(new GenericProposalModel);
model->loadContent(completions);
@@ -1030,12 +1115,14 @@ public:
ClangdCompletionAssistProvider(ClangdClient *client);
private:
- IAssistProcessor *createProcessor(const AssistInterface *assistInterface) const override;
+ IAssistProcessor *createProcessor(const AssistInterface *interface) const override;
int activationCharSequenceLength() const override { return 3; }
bool isActivationCharSequence(const QString &sequence) const override;
bool isContinuationChar(const QChar &c) const override;
+ bool isInCommentOrString(const AssistInterface *interface) const;
+
ClangdClient * const m_client;
};
@@ -1048,6 +1135,7 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
"text/x-c++hdr", "text/x-c++src", "text/x-objc++src", "text/x-objcsrc"};
setSupportedLanguage(langFilter);
setActivateDocumentAutomatically(true);
+ setLogTarget(LogTarget::Console);
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
if (!project) {
QJsonObject initOptions;
@@ -1643,7 +1731,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu
}
const Position linkPos(link.targetLine - 1, link.targetColumn);
- const QList<AstNode> astPath = getAstPath(ast, Range(linkPos, linkPos));
+ const QList<AstNode> astPath = getAstPath(ast, linkPos);
bool isVar = false;
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
if (it->role() == "declaration" && it->kind() == "Function") {
@@ -2128,7 +2216,8 @@ class ExtraHighlightingResultsCollector
{
public:
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
- HighlightingResults &results, const AstNode &ast,
+ HighlightingResults &results,
+ const Utils::FilePath &filePath, const AstNode &ast,
const QTextDocument *doc, const QString &docContent);
void collect();
@@ -2145,6 +2234,7 @@ private:
QFutureInterface<HighlightingResult> &m_future;
HighlightingResults &m_results;
+ const Utils::FilePath m_filePath;
const AstNode &m_ast;
const QTextDocument * const m_doc;
const QString &m_docContent;
@@ -2174,7 +2264,7 @@ static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const
if (!wasIfdefedOut)
rangeStartPos = doc->findBlockByNumber(it->line - 1).position();
const int pos = Utils::Text::positionInText(doc, it->line, it->column);
- const QStringView content(QStringView(docContent).mid(pos, it->length).trimmed());
+ const QStringView content = subViewLen(docContent, pos, it->length).trimmed();
if (!content.startsWith(QLatin1String("#if"))
&& !content.startsWith(QLatin1String("#elif"))
&& !content.startsWith(QLatin1String("#else"))
@@ -2210,6 +2300,7 @@ static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const
}
static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
+ const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents, const AstNode &ast,
const QPointer<TextEditorWidget> &widget,
@@ -2222,13 +2313,17 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
}
const QTextDocument doc(docContents);
- const auto isOutputParameter = [&ast](const ExpandedSemanticToken &token) {
+ const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
+ const Position startPos(token.line - 1, token.column - 1);
+ const Position endPos = startPos.withOffset(token.length, &doc);
+ return Range(startPos, endPos);
+ };
+ const auto isOutputParameter = [&ast, &doc, &tokenRange](const ExpandedSemanticToken &token) {
if (token.modifiers.contains("usedAsMutableReference"))
return true;
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
return false;
- const Position pos(token.line - 1, token.column - 1);
- const QList<AstNode> path = getAstPath(ast, Range(pos, pos));
+ const QList<AstNode> path = getAstPath(ast, tokenRange(token));
if (path.size() < 2)
return false;
if (path.last().hasConstType())
@@ -2249,7 +2344,8 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
};
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
- = [&ast, &isOutputParameter, &clangdVersion](const ExpandedSemanticToken &token) {
+ = [&ast, &isOutputParameter, &clangdVersion, &tokenRange]
+ (const ExpandedSemanticToken &token) {
TextStyles styles;
if (token.type == "variable") {
if (token.modifiers.contains("functionScope")) {
@@ -2263,8 +2359,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
} else if (token.type == "function" || token.type == "method") {
styles.mainStyle = token.modifiers.contains("virtual") ? C_VIRTUAL_METHOD : C_FUNCTION;
if (ast.isValid()) {
- const Position pos(token.line - 1, token.column - 1);
- const QList<AstNode> path = getAstPath(ast, Range(pos, pos));
+ const QList<AstNode> path = getAstPath(ast, tokenRange(token));
if (path.length() > 1) {
const AstNode declNode = path.at(path.length() - 2);
if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") {
@@ -2283,8 +2378,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
// clang hardly ever differentiates between constructors and the associated class,
// whereas we highlight constructors as functions.
if (ast.isValid()) {
- const Position pos(token.line - 1, token.column - 1);
- const QList<AstNode> path = getAstPath(ast, Range(pos, pos));
+ const QList<AstNode> path = getAstPath(ast, tokenRange(token));
if (!path.isEmpty()) {
if (path.last().kind() == "CXXConstructor") {
if (!path.last().arcanaContains("implicit"))
@@ -2338,7 +2432,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
if (widget && widget->textDocument()->document()->revision() == docRevision)
widget->setIfdefedOutBlocks(ifdefedOutBlocks);
}, Qt::QueuedConnection);
- ExtraHighlightingResultsCollector(future, results, ast, &doc, docContents).collect();
+ ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect();
if (!future.isCanceled()) {
qCDebug(clangdLog) << "reporting" << results.size() << "highlighting results";
future.reportResults(QVector<HighlightingResult>(results.cbegin(),
@@ -2378,10 +2472,12 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
IEditor * const editor = Utils::findOrDefault(EditorManager::visibleEditors(),
[doc](const IEditor *editor) { return editor->document() == doc; });
const auto editorWidget = TextEditorWidget::fromEditor(editor);
- const auto runner = [tokens, text = doc->document()->toPlainText(), ast,
+ const auto runner = [tokens, filePath = doc->filePath(),
+ text = doc->document()->toPlainText(), ast,
w = QPointer(editorWidget), rev = doc->document()->revision(),
clangdVersion = q->versionNumber()] {
- return Utils::runAsync(semanticHighlighter, tokens, text, ast, w, rev, clangdVersion);
+ return Utils::runAsync(semanticHighlighter, filePath, tokens, text, ast, w, rev,
+ clangdVersion);
};
if (isTesting) {
@@ -2543,21 +2639,28 @@ ClangdClient::ClangdCompletionAssistProvider::ClangdCompletionAssistProvider(Cla
{}
IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor(
- const AssistInterface *assistInterface) const
-{
- ClangCompletionContextAnalyzer contextAnalyzer(assistInterface->textDocument(),
- assistInterface->position(), false, {});
+ const AssistInterface *interface) const
+{
+ qCDebug(clangdLogCompletion) << "completion processor requested for" << interface->filePath();
+ qCDebug(clangdLogCompletion) << "text before cursor is"
+ << interface->textAt(interface->position(), -10);
+ qCDebug(clangdLogCompletion) << "text after cursor is"
+ << interface->textAt(interface->position(), 10);
+ ClangCompletionContextAnalyzer contextAnalyzer(interface->textDocument(),
+ interface->position(), false, {});
contextAnalyzer.analyze();
switch (contextAnalyzer.completionAction()) {
case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen:
- qCDebug(clangdLog) << "completion changed to function hint";
+ qCDebug(clangdLogCompletion) << "creating function hint processor";
return new ClangdFunctionHintProcessor(m_client);
case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
+ qCDebug(clangdLogCompletion) << "creating doxygen processor";
return new CustomAssistProcessor(m_client,
contextAnalyzer.positionForProposal(),
contextAnalyzer.completionOperator(),
CustomAssistMode::Doxygen);
case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
+ qCDebug(clangdLogCompletion) << "creating macro processor";
return new CustomAssistProcessor(m_client,
contextAnalyzer.positionForProposal(),
contextAnalyzer.completionOperator(),
@@ -2565,9 +2668,11 @@ IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor(
default:
break;
}
- const QString snippetsGroup = contextAnalyzer.addSnippets()
+ const QString snippetsGroup = contextAnalyzer.addSnippets() && !isInCommentOrString(interface)
? CppEditor::Constants::CPP_SNIPPETS_GROUP_ID
: QString();
+ qCDebug(clangdLogCompletion) << "creating proper completion processor"
+ << (snippetsGroup.isEmpty() ? "without" : "with") << "snippets";
return new ClangdCompletionAssistProcessor(m_client, snippetsGroup);
}
@@ -2588,6 +2693,7 @@ bool ClangdClient::ClangdCompletionAssistProvider::isActivationCharSequence(cons
// contexts, such as '(', '<' or '/'.
switch (kind) {
case T_DOT: case T_COLON_COLON: case T_ARROW: case T_DOT_STAR: case T_ARROW_STAR: case T_POUND:
+ qCDebug(clangdLogCompletion) << "detected" << sequence << "as activation char sequence";
return true;
}
return false;
@@ -2598,6 +2704,14 @@ bool ClangdClient::ClangdCompletionAssistProvider::isContinuationChar(const QCha
return CppEditor::isValidIdentifierChar(c);
}
+bool ClangdClient::ClangdCompletionAssistProvider::isInCommentOrString(
+ const AssistInterface *interface) const
+{
+ LanguageFeatures features = LanguageFeatures::defaultFeatures();
+ features.objCEnabled = CppEditor::ProjectFile::isObjC(interface->filePath().toString());
+ return CppEditor::isInCommentOrString(interface, features);
+}
+
void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
int /*basePosition*/) const
{
@@ -2607,35 +2721,27 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
if (!edit)
return;
- const auto kind = static_cast<CompletionItemKind::Kind>(
- item.kind().value_or(CompletionItemKind::Text));
- if (kind != CompletionItemKind::Function && kind != CompletionItemKind::Method
- && kind != CompletionItemKind::Constructor) {
- applyTextEdit(manipulator, *edit, true);
- return;
- }
-
const QString rawInsertText = edit->newText();
const int firstParenOffset = rawInsertText.indexOf('(');
const int lastParenOffset = rawInsertText.lastIndexOf(')');
- if (firstParenOffset == -1 || lastParenOffset == -1) {
- applyTextEdit(manipulator, *edit, true);
- return;
- }
-
const QString detail = item.detail().value_or(QString());
const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
QString textToBeInserted = rawInsertText.left(firstParenOffset);
QString extraCharacters;
+ int extraLength = 0;
int cursorOffset = 0;
bool setAutoCompleteSkipPos = false;
- const QTextDocument * const doc = manipulator.textCursorAt(
- manipulator.currentPosition()).document();
+ int currentPos = manipulator.currentPosition();
+ const QTextDocument * const doc = manipulator.textCursorAt(currentPos).document();
const Range range = edit->range();
const int rangeStart = range.start().toPositionInDocument(doc);
- const int rangeLength = range.end().toPositionInDocument(doc) - rangeStart;
- if (completionSettings.m_autoInsertBrackets) {
+ const auto kind = static_cast<CompletionItemKind::Kind>(
+ item.kind().value_or(CompletionItemKind::Text));
+ const bool isFunctionLike = kind == CompletionItemKind::Function
+ || kind == CompletionItemKind::Method || kind == CompletionItemKind::Constructor
+ || (firstParenOffset != -1 && lastParenOffset != -1);
+ if (isFunctionLike && completionSettings.m_autoInsertBrackets) {
// If the user typed the opening parenthesis, they'll likely also type the closing one,
// in which case it would be annoying if we put the cursor after the already automatically
// inserted closing parenthesis.
@@ -2663,7 +2769,7 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
// If the function doesn't return anything, automatically place the semicolon,
// unless we're doing a scope completion (then it might be function definition).
- const QChar characterAtCursor = manipulator.characterAt(manipulator.currentPosition());
+ const QChar characterAtCursor = manipulator.characterAt(currentPos);
bool endWithSemicolon = typedChar == ';';
const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar;
if (endWithSemicolon && characterAtCursor == semicolon) {
@@ -2679,7 +2785,7 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
typedChar = {};
}
} else {
- const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
+ const QChar lookAhead = manipulator.characterAt(currentPos + 1);
if (MatchingText::shouldInsertMatchingText(lookAhead)) {
extraCharacters += ')';
--cursorOffset;
@@ -2701,9 +2807,26 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
--cursorOffset;
}
- textToBeInserted += extraCharacters;
+ // Avoid inserting characters that are already there
+ QTextCursor cursor = manipulator.textCursorAt(rangeStart);
+ cursor.movePosition(QTextCursor::EndOfWord);
+ const QString textAfterCursor = manipulator.textAt(currentPos, cursor.position() - currentPos);
+ if (textToBeInserted != textAfterCursor
+ && textToBeInserted.indexOf(textAfterCursor, currentPos - rangeStart) >= 0) {
+ currentPos = cursor.position();
+ }
+ for (int i = 0; i < extraCharacters.length(); ++i) {
+ const QChar a = extraCharacters.at(i);
+ const QChar b = manipulator.characterAt(currentPos + i);
+ if (a == b)
+ ++extraLength;
+ else
+ break;
+ }
- const bool isReplaced = manipulator.replace(rangeStart, rangeLength, textToBeInserted);
+ textToBeInserted += extraCharacters;
+ const int length = currentPos - rangeStart + extraLength;
+ const bool isReplaced = manipulator.replace(rangeStart, length, textToBeInserted);
manipulator.setCursorPosition(rangeStart + textToBeInserted.length());
if (isReplaced) {
if (cursorOffset)
@@ -2806,10 +2929,11 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
- const AstNode &ast, const QTextDocument *doc, const QString &docContent)
- : m_future(future), m_results(results), m_ast(ast), m_doc(doc), m_docContent(docContent)
+ const Utils::FilePath &filePath, const AstNode &ast, const QTextDocument *doc,
+ const QString &docContent)
+ : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
+ m_docContent(docContent)
{
-
}
void ExtraHighlightingResultsCollector::collect()
@@ -2881,7 +3005,7 @@ void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1,
int searchStart2, int searchEnd2)
{
const int openingAngleBracketPos = onlyIndexOf(
- QStringView(m_docContent).mid(searchStart1, searchEnd1 - searchStart1),
+ subViewEnd(m_docContent, searchStart1, searchEnd1),
QStringView(QStringLiteral("<")));
if (openingAngleBracketPos == -1)
return;
@@ -2891,7 +3015,7 @@ void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1,
if (searchStart2 >= searchEnd2)
return;
const int closingAngleBracketPos = onlyIndexOf(
- QStringView(m_docContent).mid(searchStart2, searchEnd2 - searchStart2),
+ subViewEnd(m_docContent, searchStart2, searchEnd2),
QStringView(QStringLiteral(">")));
if (closingAngleBracketPos == -1)
return;
@@ -2926,6 +3050,8 @@ void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult
void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
{
+ if (node.kind() == "UserDefinedLiteral")
+ return;
if (node.kind().endsWith("Literal")) {
HighlightingResult result;
result.useTextSyles = true;
@@ -2969,16 +3095,14 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
// sub-expressions 2 and 3.
const int searchStartPosQuestionMark = posForNodeEnd(children.first());
const int searchEndPosQuestionMark = posForNodeStart(children.at(1));
- QStringView content = QStringView(m_docContent).mid(
- searchStartPosQuestionMark,
- searchEndPosQuestionMark - searchStartPosQuestionMark);
+ QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark,
+ searchEndPosQuestionMark);
const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?")));
if (questionMarkPos == -1)
return;
const int searchStartPosColon = posForNodeEnd(children.at(1));
const int searchEndPosColon = posForNodeStart(children.at(2));
- content = QStringView(m_docContent).mid(searchStartPosColon,
- searchEndPosColon - searchStartPosColon);
+ content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon);
const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":")));
if (colonPos == -1)
return;
@@ -3155,8 +3279,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
if (isDeclaration)
result.textStyles.mixinStyles.push_back(C_DECLARATION);
- const QStringView nodeText = QStringView(m_docContent)
- .mid(nodeStartPos, nodeEndPos - nodeStartPos);
+ const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos);
if (isCallToNew || isCallToDelete) {
result.line = node.range().start().line() + 1;
@@ -3255,12 +3378,22 @@ void ExtraHighlightingResultsCollector::visitNode(const AstNode &node)
{
if (m_future.isCanceled())
return;
- collectFromNode(node);
- const auto children = node.children();
- if (!children)
+ switch (node.fileStatus(m_filePath)) {
+ case AstNode::FileStatus::Foreign:
return;
- for (const AstNode &childNode : *children)
- visitNode(childNode);
+ case AstNode::FileStatus::Ours:
+ case AstNode::FileStatus::Unknown:
+ collectFromNode(node);
+ [[fallthrough]];
+ case ClangCodeModel::Internal::AstNode::FileStatus::Mixed: {
+ const auto children = node.children();
+ if (!children)
+ return;
+ for (const AstNode &childNode : *children)
+ visitNode(childNode);
+ break;
+ }
+ }
}
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp
index ffdd3c4396..195c4f4ead 100644
--- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp
+++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp
@@ -104,9 +104,9 @@ public:
}
QWidget *createWidget(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
- const std::function<bool()> &canApplyFixIt)
+ const std::function<bool()> &canApplyFixIt, const QString &source)
{
- const QString text = htmlText(diagnostics);
+ const QString text = htmlText(diagnostics, source);
auto *label = new QLabel;
label->setTextFormat(Qt::RichText);
@@ -154,13 +154,20 @@ public:
return label;
}
- QString htmlText(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics)
+ QString htmlText(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
+ const QString &source)
{
// For debugging, add: style='border-width:1px;border-color:black'
QString text = "<table cellspacing='0' cellpadding='0' width='100%'>";
foreach (const ClangBackEnd::DiagnosticContainer &diagnostic, diagnostics)
text.append(tableRows(diagnostic));
+ if (!source.isEmpty()) {
+ text.append(QString::fromUtf8("<tr><td colspan='2' align='left'>"
+ "<font color='gray'>%1</font></td></tr>")
+ .arg(QCoreApplication::translate("ClangDiagnosticWidget", "[Source: %1]"))
+ .arg(source));
+ }
text.append("</table>");
@@ -396,7 +403,8 @@ QString ClangDiagnosticWidget::createText(
const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
const ClangDiagnosticWidget::Destination &destination)
{
- const QString htmlText = WidgetFromDiagnostics(toHints(destination, {})).htmlText(diagnostics);
+ const QString htmlText = WidgetFromDiagnostics(toHints(destination, {}))
+ .htmlText(diagnostics, {});
QTextDocument document;
document.setHtml(htmlText);
@@ -410,11 +418,13 @@ QString ClangDiagnosticWidget::createText(
return text;
}
-QWidget *ClangDiagnosticWidget::createWidget(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
- const Destination &destination, const std::function<bool()> &canApplyFixIt)
+QWidget *ClangDiagnosticWidget::createWidget(
+ const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
+ const Destination &destination, const std::function<bool()> &canApplyFixIt,
+ const QString &source)
{
return WidgetFromDiagnostics(toHints(destination, canApplyFixIt))
- .createWidget(diagnostics, canApplyFixIt);
+ .createWidget(diagnostics, canApplyFixIt, source);
}
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h
index afe35812ae..92f98a59ca 100644
--- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h
+++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h
@@ -48,7 +48,8 @@ public:
static QWidget *createWidget(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
const Destination &destination,
- const std::function<bool()> &canApplyFixIt);
+ const std::function<bool()> &canApplyFixIt,
+ const QString &source);
};
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp
index a9d4f01c72..94a4408505 100644
--- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp
+++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp
@@ -506,7 +506,8 @@ ClangEditorDocumentProcessor::creatorForHeaderErrorDiagnosticWidget(
vbox->setSpacing(2);
vbox->addWidget(ClangDiagnosticWidget::createWidget({firstHeaderErrorDiagnostic},
- ClangDiagnosticWidget::InfoBar, {}));
+ ClangDiagnosticWidget::InfoBar, {},
+ "libclang"));
auto widget = new QWidget;
widget->setLayout(vbox);
diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
index 52be361305..d1c6b0d0ce 100644
--- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
+++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
@@ -286,7 +286,7 @@ void ClangModelManagerSupport::updateLanguageClient(
if (const ProjectExplorer::Target * const target = project->activeTarget()) {
if (const ProjectExplorer::BuildConfiguration * const bc
= target->activeBuildConfiguration()) {
- return bc->buildDirectory();
+ return bc->buildDirectory() / ".qtc_clangd";
}
}
return Utils::FilePath();
@@ -363,7 +363,7 @@ void ClangModelManagerSupport::updateLanguageClient(
});
});
- auto future = Utils::runAsync(&Internal::generateCompilationDB, projectInfo,
+ auto future = Utils::runAsync(&Internal::generateCompilationDB, projectInfo, jsonDbDir,
CompilationDbPurpose::CodeModel,
warningsConfigForProject(project),
optionsForProject(project));
@@ -483,10 +483,6 @@ void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor)
// TODO: Ensure that not fully loaded documents are updated?
- // TODO: If the file does not belong to any project and it is a header file,
- // it might make sense to check whether the file is included by any file
- // that does belong to a project, and if so, use the respective client
- // instead. Is this feasible?
ProjectExplorer::Project * const project
= ProjectExplorer::SessionManager::projectForFile(document->filePath());
if (ClangdClient * const client = clientForProject(project))
diff --git a/src/plugins/clangcodemodel/clangtextmark.cpp b/src/plugins/clangcodemodel/clangtextmark.cpp
index 38227008a9..01cfae6ae6 100644
--- a/src/plugins/clangcodemodel/clangtextmark.cpp
+++ b/src/plugins/clangcodemodel/clangtextmark.cpp
@@ -281,7 +281,7 @@ bool ClangTextMark::addToolTipContent(QLayout *target) const
&& diagMgr->diagnosticsWithFixIts().contains(diag);
};
QWidget *widget = ClangDiagnosticWidget::createWidget(
- {m_diagnostic}, ClangDiagnosticWidget::ToolTip, canApplyFixIt);
+ {m_diagnostic}, ClangDiagnosticWidget::ToolTip, canApplyFixIt, "libclang");
target->addWidget(widget);
return true;
@@ -398,7 +398,7 @@ bool ClangdTextMark::addToolTipContent(QLayout *target) const
return c && c->reachable() && c->hasDiagnostic(DocumentUri::fromFilePath(fp), diag);
};
target->addWidget(ClangDiagnosticWidget::createWidget({m_diagnostic},
- ClangDiagnosticWidget::ToolTip, canApplyFixIt));
+ ClangDiagnosticWidget::ToolTip, canApplyFixIt, "clangd"));
return true;
}
diff --git a/src/plugins/clangcodemodel/clangutils.cpp b/src/plugins/clangcodemodel/clangutils.cpp
index bbe9817380..a089248d03 100644
--- a/src/plugins/clangcodemodel/clangutils.cpp
+++ b/src/plugins/clangcodemodel/clangutils.cpp
@@ -372,18 +372,15 @@ static QJsonObject createFileObject(const FilePath &buildDir,
}
GenerateCompilationDbResult generateCompilationDB(const CppEditor::ProjectInfo::ConstPtr projectInfo,
+ const Utils::FilePath &baseDir,
CompilationDbPurpose purpose,
const ClangDiagnosticConfig &warningsConfig,
const QStringList &projectOptions)
{
- const FilePath buildDir = projectInfo->buildRoot();
- QTC_ASSERT(!buildDir.isEmpty(), return GenerateCompilationDbResult(QString(),
+ QTC_ASSERT(!baseDir.isEmpty(), return GenerateCompilationDbResult(QString(),
QCoreApplication::translate("ClangUtils", "Could not retrieve build directory.")));
-
- QDir dir(buildDir.toString());
- if (!dir.exists())
- dir.mkpath(dir.path());
- QFile compileCommandsFile(buildDir.toString() + "/compile_commands.json");
+ QTC_CHECK(baseDir.ensureWritableDir());
+ QFile compileCommandsFile(baseDir.toString() + "/compile_commands.json");
const bool fileOpened = compileCommandsFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!fileOpened) {
return GenerateCompilationDbResult(QString(),
@@ -397,7 +394,7 @@ GenerateCompilationDbResult generateCompilationDB(const CppEditor::ProjectInfo::
if (purpose == CompilationDbPurpose::Project)
args = projectPartArguments(*projectPart);
for (const ProjectFile &projFile : projectPart->files) {
- const QJsonObject json = createFileObject(buildDir, args, *projectPart, projFile,
+ const QJsonObject json = createFileObject(baseDir, args, *projectPart, projFile,
purpose, warningsConfig, projectOptions);
if (compileCommandsFile.size() > 1)
compileCommandsFile.write(",");
diff --git a/src/plugins/clangcodemodel/clangutils.h b/src/plugins/clangcodemodel/clangutils.h
index 1d46e6a49b..c3a496f779 100644
--- a/src/plugins/clangcodemodel/clangutils.h
+++ b/src/plugins/clangcodemodel/clangutils.h
@@ -88,8 +88,8 @@ public:
enum class CompilationDbPurpose { Project, CodeModel };
GenerateCompilationDbResult generateCompilationDB(const CppEditor::ProjectInfo::ConstPtr projectInfo,
- CompilationDbPurpose purpose, const CppEditor::ClangDiagnosticConfig &warningsConfig,
- const QStringList &projectOptions);
+ const Utils::FilePath &baseDir, CompilationDbPurpose purpose,
+ const CppEditor::ClangDiagnosticConfig &warningsConfig, const QStringList &projectOptions);
class DiagnosticTextInfo
{