diff --git a/docs/API/knut/cppdocument.md b/docs/API/knut/cppdocument.md
index 268652e3..0fb49c69 100644
--- a/docs/API/knut/cppdocument.md
+++ b/docs/API/knut/cppdocument.md
@@ -298,6 +298,7 @@ The returned QueryMatch instances contain the following captures:
- `declaration`: The full declaration of the method
- `function`: The function declaration, without the return type
- `name`: The name of the function
+- `return-type`: The return type of the function without any reference/pointer specifiers (i.e. `&`/`*`)
#### array<[QueryMatch](../knut/querymatch.md)> **queryMethodDefinition**(string scope, string methodName)
diff --git a/src/core/codedocument.cpp b/src/core/codedocument.cpp
index 8fac2a76..871da29b 100644
--- a/src/core/codedocument.cpp
+++ b/src/core/codedocument.cpp
@@ -848,4 +848,10 @@ AstNode CodeDocument::astNodeAt(int pos)
return {};
}
+QList CodeDocument::includedRanges() const
+{
+ // An empty list tells the parser to include the entire document.
+ return {};
+}
+
} // namespace Core
diff --git a/src/core/codedocument.h b/src/core/codedocument.h
index df1c476d..4a0dc4f6 100644
--- a/src/core/codedocument.h
+++ b/src/core/codedocument.h
@@ -15,6 +15,7 @@
#include "querymatch.h"
#include "symbol.h"
#include "textdocument.h"
+#include "treesitter/parser.h"
#include "treesitter/query.h"
#include
@@ -79,6 +80,8 @@ class CodeDocument : public TextDocument
Q_INVOKABLE Core::AstNode astNodeAt(int pos);
+ virtual QList includedRanges() const;
+
public slots:
void selectSymbol(const QString &name, int options = NoFindFlags);
diff --git a/src/core/codedocument_p.cpp b/src/core/codedocument_p.cpp
index ce0212c7..bcfd8f55 100644
--- a/src/core/codedocument_p.cpp
+++ b/src/core/codedocument_p.cpp
@@ -48,7 +48,12 @@ treesitter::Parser &TreeSitterHelper::parser()
std::optional &TreeSitterHelper::syntaxTree()
{
if (!m_tree) {
- m_tree = parser().parseString(m_document->text());
+ auto &parser = this->parser();
+ if (!parser.setIncludedRanges(m_document->includedRanges())) {
+ spdlog::warn("TreeSitterHelper::syntaxTree: Unable to set the included ranges on the treesitter parser!");
+ parser.setIncludedRanges({});
+ }
+ m_tree = parser.parseString(m_document->text());
if (!m_tree) {
spdlog::warn("CodeDocument::syntaxTree: Failed to parse document {}!", m_document->fileName());
}
diff --git a/src/core/core/settings.json b/src/core/core/settings.json
index 9fc1a0b1..d448136b 100644
--- a/src/core/core/settings.json
+++ b/src/core/core/settings.json
@@ -30,6 +30,13 @@
"LANG_NEUTRAL": "[default]"
}
},
+ "cpp": {
+ "excluded_macros": [
+ "AFX_EXT_CLASS",
+ "[A-Z_]*EXPORT[A-Z_]*",
+ "Q_OBJECT"
+ ]
+ },
"mime_types": {
"c": "cpp_type",
"cpp": "cpp_type",
diff --git a/src/core/cppdocument.cpp b/src/core/cppdocument.cpp
index 656c9847..a42b0687 100644
--- a/src/core/cppdocument.cpp
+++ b/src/core/cppdocument.cpp
@@ -692,6 +692,7 @@ MessageMap CppDocument::mfcExtractMessageMap(const QString &className /* = ""*/)
* - `declaration`: The full declaration of the method
* - `function`: The function declaration, without the return type
* - `name`: The name of the function
+ * - `return-type`: The return type of the function without any reference/pointer specifiers (i.e. `&`/`*`)
*/
Core::QueryMatchList CppDocument::queryMethodDeclaration(const QString &className, const QString &functionName)
{
@@ -1545,4 +1546,75 @@ QStringList CppDocument::primitiveTypes() const
return Utils::cppPrimitiveTypes();
}
+QList CppDocument::includedRanges() const
+{
+ auto macros = Settings::instance()->value(Settings::CppExcludedMacros);
+ if (macros.isEmpty()) {
+ return {};
+ }
+
+ QRegularExpression regex(macros.join("|"));
+ if (!regex.isValid()) {
+ spdlog::error("CppDocument::includedRanges: Failed to create regex for excluded macros: {}",
+ regex.errorString());
+ return {};
+ }
+
+ auto document = textEdit()->document();
+
+ QList ranges;
+ treesitter::Point lastPoint {0, 0};
+ uint32_t lastByte = 0;
+
+ for (auto block = document->firstBlock(); block.isValid(); block = block.next()) {
+ QRegularExpressionMatch match;
+ auto searchFrom = 0;
+ auto index = block.text().indexOf(regex, searchFrom, &match);
+
+ // Run this in a loop to support multiple macros on the same line.
+ while (index != -1) {
+ // We need to construct a range from the end of the last match to the start of the current match.
+ //
+ // Note that the ranges have an inclusive start and an exclusive end..
+ //
+ // Also Note that the column seems to be in bytes, not characters.
+ // This is why we multiply by sizeof(QChar) to get the correct column.
+ // At least that's what the TreeSitterInspector shows us.
+ auto endPoint = treesitter::Point {.row = static_cast(block.blockNumber()),
+ .column = static_cast(index * sizeof(QChar))};
+ ranges.push_back({.start_point = lastPoint,
+ .end_point = endPoint,
+ .start_byte = lastByte,
+ // No need to add - 1 here, the ranges are exclusive at the end.
+ .end_byte = static_cast((block.position() + index) * sizeof(QChar))});
+
+ auto matchLength = match.capturedLength();
+ lastByte = static_cast((block.position() + index + matchLength) * sizeof(QChar));
+ lastPoint = {.row = static_cast(block.blockNumber()),
+ .column = static_cast((index + matchLength) * sizeof(QChar))};
+ if (lastPoint.column == static_cast(block.length())) {
+ ++lastPoint.row;
+ lastPoint.column = 0;
+ }
+
+ searchFrom = index + matchLength;
+ index = block.text().indexOf(regex, searchFrom, &match);
+ }
+ }
+
+ if (!ranges.isEmpty()) {
+ // Add the last range, up to the end of the document, but only if we have another range.
+ // Leaving the ranges empty will parse the entire document, so that's easiest.
+ auto endPoint =
+ treesitter::Point {.row = static_cast(document->blockCount() - 1),
+ .column = static_cast(document->lastBlock().length() * sizeof(QChar))};
+ ranges.push_back({.start_point = lastPoint,
+ .end_point = endPoint,
+ .start_byte = lastByte,
+ .end_byte = static_cast(document->characterCount() * sizeof(QChar))});
+ }
+
+ return ranges;
+}
+
} // namespace Core
diff --git a/src/core/cppdocument.h b/src/core/cppdocument.h
index 00e021db..cab1f701 100644
--- a/src/core/cppdocument.h
+++ b/src/core/cppdocument.h
@@ -60,6 +60,8 @@ class CppDocument : public CodeDocument
bool changeBaseClass(CppDocument *header, CppDocument *source, const QString &className,
const QString &newClassBaseName);
+ QList includedRanges() const override;
+
public slots:
Core::CppDocument *openHeaderSource();
diff --git a/src/core/settings.h b/src/core/settings.h
index 463db5d3..0a332311 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -49,6 +49,7 @@ class Settings : public QObject
static inline constexpr char RcAssetFlags[] = "/rc/asset_flags";
static inline constexpr char RcAssetColors[] = "/rc/asset_transparent_colors";
static inline constexpr char RcLanguageMap[] = "/rc/language_map";
+ static inline constexpr char CppExcludedMacros[] = "/cpp/excluded_macros";
static inline constexpr char SaveLogsToFile[] = "/logs/saveToFile";
static inline constexpr char ScriptPaths[] = "/script_paths";
static inline constexpr char Tab[] = "/text_editor/tab";
diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp
index b7a57bb2..e4eb0ed2 100644
--- a/src/gui/optionsdialog.cpp
+++ b/src/gui/optionsdialog.cpp
@@ -19,7 +19,9 @@
#include
#include
#include
+#include
#include
+#include
#include
#include
@@ -45,6 +47,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
initializeRcSettings();
initializeSaveToLogFileSetting();
initializeEnableLSPSetting();
+ initializeCppSettings();
updateScriptPaths();
}
@@ -217,6 +220,42 @@ void OptionsDialog::initializeRcSettings()
});
}
+void OptionsDialog::initializeCppSettings()
+{
+ auto *model = new QStringListModel(this);
+ model->setStringList(DEFAULT_VALUE(QStringList, CppExcludedMacros));
+ ui->cppExcludedMacros->setModel(model);
+ ui->cppExcludedMacros->setSelectionMode(QAbstractItemView::SingleSelection);
+
+ connect(ui->cppAddExcludedMacro, &QPushButton::clicked, model, [this, model]() {
+ auto ok = false;
+ auto excludeMacro = QInputDialog::getText(this, tr("Exclude a new macro"), tr("Macro name (supports Regex)"),
+ QLineEdit::Normal, "", &ok);
+ if (ok) {
+ if (model->insertRows(0, 1)) {
+ model->setData(model->index(0), excludeMacro);
+ } else {
+ spdlog::warn("OptionsDialog::excludeMacro: Failed to create new row!");
+ }
+ }
+ });
+
+ connect(ui->cppRemoveExcludedMacro, &QPushButton::clicked, model, [this, model]() {
+ auto selection = ui->cppExcludedMacros->selectionModel()->selectedRows();
+ // single selection mode, so should be at most one.
+ if (!selection.isEmpty()) {
+ model->removeRows(selection.front().row(), 1);
+ }
+ });
+
+ connect(model, &QStringListModel::dataChanged, model, [model]() {
+ SET_DEFAULT_VALUE(CppExcludedMacros, model->stringList());
+ });
+ connect(model, &QStringListModel::rowsRemoved, model, [model]() {
+ SET_DEFAULT_VALUE(CppExcludedMacros, model->stringList());
+ });
+}
+
void OptionsDialog::openUserSettings()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(ui->userPath->text()));
diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h
index c05f7020..d7108b5e 100644
--- a/src/gui/optionsdialog.h
+++ b/src/gui/optionsdialog.h
@@ -35,6 +35,7 @@ class OptionsDialog : public QDialog
void initializeScriptPathSettings();
void initializeScriptBehaviorSettings();
void initializeRcSettings();
+ void initializeCppSettings();
void openUserSettings();
void openProjectSettings();
diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui
index 32561bba..2d2f4f7c 100644
--- a/src/gui/optionsdialog.ui
+++ b/src/gui/optionsdialog.ui
@@ -626,6 +626,49 @@
+
+
+ -
+
+
+ C++ Files
+
+
+
-
+
+
+ The Macros listed here will be excluded from parsing (supports Regex).
+
+
+ Excluded Macros
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
+ Add
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+
+
+
+
+
-
@@ -684,6 +727,11 @@
Rc Files
+ -
+
+ C++ Files
+
+
diff --git a/src/gui/treesitterinspector.cpp b/src/gui/treesitterinspector.cpp
index 944cc479..49310a49 100644
--- a/src/gui/treesitterinspector.cpp
+++ b/src/gui/treesitterinspector.cpp
@@ -182,6 +182,7 @@ void TreeSitterInspector::changeText()
Core::LoggerDisabler disableLogging;
text = m_document->text();
}
+ m_parser.setIncludedRanges(m_document->includedRanges());
auto tree = m_parser.parseString(text);
if (tree.has_value()) {
m_treemodel.setTree(std::move(tree.value()), makePredicates(), ui->enableUnnamed->isChecked());
diff --git a/src/gui/treesittertreemodel.cpp b/src/gui/treesittertreemodel.cpp
index 9341a8de..d38acc82 100644
--- a/src/gui/treesittertreemodel.cpp
+++ b/src/gui/treesittertreemodel.cpp
@@ -74,11 +74,13 @@ QVariant TreeSitterTreeModel::TreeNode::data(int column) const
return QString("%1: %2").arg(fieldName, m_node.type());
}
case 1:
- return QString("[%1:%2] - [%3:%4]")
+ return QString("[%1:%2](%3) - [%4:%5](%6)")
.arg(m_node.startPoint().row)
.arg(m_node.startPoint().column)
+ .arg(m_node.startPosition())
.arg(m_node.endPoint().row)
- .arg(m_node.endPoint().column);
+ .arg(m_node.endPoint().column)
+ .arg(m_node.endPosition());
default:
break;
}
diff --git a/src/treesitter/parser.cpp b/src/treesitter/parser.cpp
index 279ae912..11959df2 100644
--- a/src/treesitter/parser.cpp
+++ b/src/treesitter/parser.cpp
@@ -58,6 +58,11 @@ std::optional Parser::parseString(const QString &text, const Tree *old_tre
return tree ? Tree(tree) : std::optional {};
}
+bool Parser::setIncludedRanges(const QList &ranges)
+{
+ return ts_parser_set_included_ranges(m_parser, ranges.data(), ranges.size());
+}
+
const TSLanguage *Parser::language() const
{
return ts_parser_language(m_parser);
diff --git a/src/treesitter/parser.h b/src/treesitter/parser.h
index 7ef69fe9..a9112e8e 100644
--- a/src/treesitter/parser.h
+++ b/src/treesitter/parser.h
@@ -12,6 +12,8 @@
#include "core/document.h"
#include
+#include
+#include
struct TSParser;
struct TSLanguage;
@@ -19,6 +21,7 @@ struct TSLanguage;
namespace treesitter {
class Tree;
+using Range = TSRange;
class Parser
{
@@ -37,6 +40,21 @@ class Parser
std::optional parseString(const QString &text, const Tree *old_tree = nullptr) const;
+ /**
+ * Parse only the given ranges.
+ * Note: if the ranges are empty, the entire document is parsed.
+ *
+ * From tree_sitter/api.h:
+ * [The] given ranges must be ordered from earliest to latest in the document,
+ * and they must not overlap. That is, the following must hold for all
+ * `i` < `length - 1`: ranges[i].end_byte <= ranges[i + 1].start_byte
+ *
+ * If this requirement is not satisfied, the operation will fail, the ranges
+ * will not be assigned, and this function will return `false`. On success,
+ * this function returns `true`
+ */
+ bool setIncludedRanges(const QList &ranges);
+
const TSLanguage *language() const;
static TSLanguage *getLanguage(Core::Document::Type type);
diff --git a/test_data/tst_cppdocument/treesitterExcludesMacros/AFX_EXT_CLASS.h b/test_data/tst_cppdocument/treesitterExcludesMacros/AFX_EXT_CLASS.h
new file mode 100644
index 00000000..4c6df75c
--- /dev/null
+++ b/test_data/tst_cppdocument/treesitterExcludesMacros/AFX_EXT_CLASS.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class AFX_EXT_CLASS TestClass : public AFX_EXT_CLASSBase{
+publicAFX_EXT_CLASS:
+ void testMethod();
+
+private:
+ int AFX_EXT_CLASS m_count;
+};
+AFX_EXT_CLASS
diff --git a/tests/tst_cppdocument_treesitter.cpp b/tests/tst_cppdocument_treesitter.cpp
index b250a634..5ae65991 100644
--- a/tests/tst_cppdocument_treesitter.cpp
+++ b/tests/tst_cppdocument_treesitter.cpp
@@ -358,6 +358,29 @@ private slots:
QVERIFY(headerFile.compare());
}
}
+
+ void excludeMacros()
+ {
+ Test::testCppDocument("tst_cppdocument/treesitterExcludesMacros", "AFX_EXT_CLASS.h", [](auto *document) {
+ auto match = document->queryClassDefinition("TestClass");
+ QVERIFY(!match.isEmpty());
+ QCOMPARE(match.get("name").text(), "TestClass");
+ QCOMPARE(match.get("base").text(), "Base");
+
+ match = document->queryMember("TestClass", "m_count");
+ QVERIFY(!match.isEmpty());
+ QCOMPARE(match.get("name").text(), "m_count");
+ QCOMPARE(match.get("type").text(), "int");
+ QCOMPARE(match.get("member").text(), "int AFX_EXT_CLASS m_count;");
+
+ auto matches = document->queryMethodDeclaration("TestClass", "testMethod");
+ QCOMPARE(matches.length(), 1);
+ match = matches.front();
+ QVERIFY(!match.isEmpty());
+ QCOMPARE(match.get("name").text(), "testMethod");
+ QCOMPARE(match.get("return-type").text(), "void");
+ });
+ }
};
QTEST_MAIN(TestCppDocumentTreeSitter)