35 if (value.is_boolean()) {
36 return value.get<
bool>() ?
"true" :
"false";
38 if (value.is_number_integer()) {
39 return QString::number(value.get<int64_t>());
41 if (value.is_number_unsigned()) {
42 return QString::number(value.get<uint64_t>());
44 if (value.is_number_float()) {
45 return QString::number(value.get<
double>());
47 if (value.is_string()) {
48 return QString::fromStdString(
56 auto addAttr = [&](
const char *key,
const char *label) {
57 if (props.contains(key) && props.at(key).is_boolean() &&
58 props.at(key).get<
bool>()) {
62 addAttr(
"isConstexpr",
"constexpr");
63 addAttr(
"isStatic",
"static");
64 addAttr(
"isVirtual",
"virtual");
65 addAttr(
"isPure",
"pure");
66 addAttr(
"isInlined",
"inline");
69 if (!attrs.isEmpty()) {
70 attrStr =
" {" + attrs.join(
" ") +
"}";
75 if (props.contains(
"templateArgs") && props.at(
"templateArgs").is_array()) {
76 const auto &arr = props.at(
"templateArgs");
79 for (
const auto &arg : arr) {
80 if (arg.contains(
"value")) {
81 parts << valueToString(arg.at(
"value"));
82 }
else if (arg.contains(
"kindName")) {
83 parts << QString::fromStdString(
87 templateStr =
"<" + parts.join(
", ") +
">";
93 if (props.contains(
"isLValue") && props.at(
"isLValue").is_boolean() &&
94 props.at(
"isLValue").get<
bool>()) {
96 }
else if (props.contains(
"isXValue") && props.at(
"isXValue").is_boolean() &&
97 props.at(
"isXValue").get<
bool>()) {
99 }
else if (props.contains(
"isPRValue") &&
100 props.at(
"isPRValue").is_boolean() &&
101 props.at(
"isPRValue").get<
bool>()) {
107 const bool typeDep = props.contains(
"isTypeDependent") &&
108 props.at(
"isTypeDependent").is_boolean() &&
109 props.at(
"isTypeDependent").get<
bool>();
110 const bool valDep = props.contains(
"isValueDependent") &&
111 props.at(
"isValueDependent").is_boolean() &&
112 props.at(
"isValueDependent").get<
bool>();
113 if (typeDep || valDep) {
118 auto getStr = [&](
const char *key) -> QString {
119 if (props.contains(key) && props.at(key).is_string()) {
120 return QString::fromStdString(
125 QString type = getStr(
"type");
126 if (type.isEmpty()) {
127 type = getStr(
"spelledType");
129 QString canonical = getStr(
"canonicalType");
130 QString desugared = getStr(
"desugaredType");
133 if (!canonical.isEmpty() && canonical == type) {
136 if (!desugared.isEmpty() && desugared == type) {
140 QString canonicalPart;
141 if (!canonical.isEmpty()) {
142 canonicalPart = QString(
" canon: %1").arg(canonical);
145 if (!desugared.isEmpty()) {
146 desugPart = QString(
" desug: %1").arg(desugared);
149 QString templatePart;
150 if (!templateStr.isEmpty()) {
151 templatePart =
" " + templateStr;
155 if (props.contains(
"value")) {
156 QString v = valueToString(props.at(
"value"));
158 valuePart = QString(
" = %1").arg(v);
162 QString combined = canonicalPart + desugPart + attrStr + templatePart + vc +
164 return combined.trimmed();
171AstModel::AstModel(QObject *parent)
172 : QAbstractItemModel(parent), root_(nullptr),
173 emptyMessage_(tr(
"No AST available (code not yet compiled).")) {}
175AstModel::~AstModel() {
181 if (emptyMessage_ == message) {
185 emptyMessage_ = message;
192 selectedNode_ =
nullptr;
199 selectedNode_ =
nullptr;
205 return static_cast<int>(totalNodeCount_);
208QModelIndex AstModel::index(
int row,
int column,
209 const QModelIndex &parent)
const {
210 if (!hasIndex(row, column, parent)) {
211 return QModelIndex();
214 if (!parent.isValid()) {
216 return QModelIndex();
219 if (emptyMessage_.isEmpty()) {
220 return QModelIndex();
222 return createIndex(row, column,
static_cast<void *
>(
nullptr));
224 return createIndex(row, column, root_);
227 AstViewNode *parentNode = getNodeFromIndex(parent);
229 return QModelIndex();
231 const std::vector<AstViewNode *> &children = visibleChildrenFor(parentNode);
232 if (row < 0 || row >=
static_cast<int>(children.size())) {
233 return QModelIndex();
236 AstViewNode *childNode = children[row];
237 return createIndex(row, column, childNode);
240QModelIndex AstModel::parent(
const QModelIndex &child)
const {
241 if (!child.isValid()) {
242 return QModelIndex();
245 AstViewNode *childNode = getNodeFromIndex(child);
247 return QModelIndex();
250 AstViewNode *parentNode = childNode->getParent();
252 return QModelIndex();
255 int row = visibleRow(parentNode);
257 return QModelIndex();
260 return createIndex(row, 0, parentNode);
263int AstModel::rowCount(
const QModelIndex &parent)
const {
265 if (!parent.isValid() && !emptyMessage_.isEmpty()) {
271 if (parent.column() > 0) {
275 if (!parent.isValid()) {
279 AstViewNode *node = getNodeFromIndex(parent);
280 return static_cast<int>(visibleChildrenFor(node).size());
283int AstModel::columnCount(
const QModelIndex &parent)
const {
288QVariant AstModel::data(
const QModelIndex &index,
int role)
const {
289 if (!index.isValid()) {
293 AstViewNode *node = getNodeFromIndex(index);
295 if (!root_ && index.row() == 0 && index.column() == 0) {
296 if (role == Qt::DisplayRole) {
297 return emptyMessage_;
299 if (role == Qt::ForegroundRole) {
300 return QBrush(QColor(128, 128, 128));
302 if (role == Qt::FontRole) {
304 font.setItalic(
true);
307 if (role == Qt::ToolTipRole) {
308 return tr(
"Double-click a source file to generate and load an AST.");
314 const AcavJson &props = node->getProperties();
316 if (role == Qt::DisplayRole && index.column() == 0) {
317 QString kind =
"<unknown>";
318 if (props.contains(
"kind") && props.at(
"kind").is_string()) {
319 kind = QString::fromStdString(
320 props.at(
"kind").get<InternedString>().str());
324 if (props.contains(
"name") && props.at(
"name").is_string()) {
325 name = QString::fromStdString(
326 props.at(
"name").get<InternedString>().str());
327 }
else if (props.contains(
"declName") && props.at(
"declName").is_string()) {
328 name = QString::fromStdString(
329 props.at(
"declName").get<InternedString>().str());
330 }
else if (props.contains(
"memberName") &&
331 props.at(
"memberName").is_string()) {
332 name = QString::fromStdString(
333 props.at(
"memberName").get<InternedString>().str());
337 if (props.contains(
"type") && props.at(
"type").is_string()) {
338 type = QString::fromStdString(
339 props.at(
"type").get<InternedString>().str());
340 }
else if (props.contains(
"spelledType") &&
341 props.at(
"spelledType").is_string()) {
342 type = QString::fromStdString(
343 props.at(
"spelledType").get<InternedString>().str());
346 QString head = name.isEmpty() ? kind : QString(
"%1 %2").arg(kind, name);
348 if (!type.isEmpty()) {
349 tail = QString(
" : %1").arg(type);
352 QString detail = buildDetailString(props);
353 if (detail.isEmpty()) {
356 return tail.isEmpty() ? (head +
" " + detail)
357 : (head + tail +
" " + detail);
360 if (role == NodePtrRole) {
361 return QVariant::fromValue(
static_cast<void *
>(node));
364 if (role == Qt::ToolTipRole) {
365 QString detail = buildDetailString(props);
366 const SourceRange &range = node->getSourceRange();
368 if (range.begin().fileID() == FileManager::InvalidFileID) {
369 loc =
"Range: <invalid>";
371 loc = QString(
"Range: %1:%2 - %3:%4")
372 .arg(range.begin().line())
373 .arg(range.begin().column())
374 .arg(range.end().line())
375 .arg(range.end().column());
377 return detail.isEmpty() ? loc : (detail +
"\n" + loc);
383QVariant AstModel::headerData(
int section, Qt::Orientation orientation,
385 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
393Qt::ItemFlags AstModel::flags(
const QModelIndex &index)
const {
394 if (!index.isValid()) {
395 return Qt::NoItemFlags;
398 if (!root_ && index.row() == 0 && index.column() == 0) {
399 return Qt::ItemIsEnabled;
402 Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
406 auto *node =
static_cast<AstViewNode *
>(index.internalPointer());
407 if (node && visibleChildrenFor(node).empty()) {
408 defaultFlags |= Qt::ItemNeverHasChildren;
414AstViewNode *AstModel::getNodeFromIndex(
const QModelIndex &index)
const {
415 return static_cast<AstViewNode *
>(index.internalPointer());
419 if (!node || !root_) {
420 qDebug() <<
"AstModel::selectNode: null node or no root";
421 return QModelIndex();
424 QModelIndex index = findNodeIndex(node);
425 if (index.isValid()) {
428 QModelIndex oldIndex;
429 if (oldSelection && oldSelection != node) {
430 oldIndex = findNodeIndex(oldSelection);
433 selectedNode_ = node;
437 if (oldIndex.isValid()) {
438 emit dataChanged(oldIndex, oldIndex, {Qt::BackgroundRole});
440 emit dataChanged(index, index, {Qt::BackgroundRole});
442 qDebug() <<
"AstModel::selectNode: findNodeIndex failed for node" << node;
449 if (!index.isValid()) {
454 if (!node || node == selectedNode_) {
459 QModelIndex oldIndex;
461 oldIndex = findNodeIndex(selectedNode_);
464 selectedNode_ = node;
467 if (oldIndex.isValid()) {
468 emit dataChanged(oldIndex, oldIndex, {Qt::BackgroundRole});
470 emit dataChanged(index, index, {Qt::BackgroundRole});
477QModelIndex AstModel::findNodeIndex(
AstViewNode *node)
const {
478 if (!node || !root_) {
479 return QModelIndex();
482 std::vector<AstViewNode *> path;
483 AstViewNode *current = node;
485 path.push_back(current);
486 current = current->getParent();
489 if (path.back() != root_) {
490 qDebug() <<
"AstModel::findNodeIndex: path does not lead to root";
491 return QModelIndex();
494 std::reverse(path.begin(), path.end());
497 for (AstViewNode *entry : path) {
498 int row = visibleRow(entry);
500 qDebug() <<
"AstModel::findNodeIndex: failed to find row for" << entry;
501 return QModelIndex();
503 index = this->index(row, 0, index);
504 if (!index.isValid()) {
505 qDebug() <<
"AstModel::findNodeIndex: failed to create index at row" << row;
506 return QModelIndex();
513const std::vector<AstViewNode *> &
514AstModel::visibleChildrenFor(AstViewNode *parent)
const {
515 static const std::vector<AstViewNode *> kEmpty;
519 return parent->getChildren();
522int AstModel::visibleRow(AstViewNode *node)
const {
527 AstViewNode *parent = node->getParent();
529 return node == root_ ? 0 : -1;
531 const auto &siblings = parent->getChildren();
532 for (std::size_t i = 0; i < siblings.size(); ++i) {
533 if (siblings[i] == node) {
534 return static_cast<int>(i);
Qt model for AST tree view display.
nlohmann::basic_json< std::map, std::vector, InternedString, bool, int64_t, uint64_t, double, std::allocator, nlohmann::adl_serializer, std::vector< uint8_t > > AcavJson
Custom JSON type using InternedString for automatic string deduplication.
Centralized file registry with path-to-FileID mapping.
Memory-efficient immutable string with automatic deduplication.
void updateSelectionFromIndex(const QModelIndex &index)
Update selection from a QModelIndex (e.g., when user clicks directly).
int visibleNodeCount() const
Number of nodes currently visible.
void clear()
Clear model and delete AST.
void setRootNode(AstViewNode *root)
Set the root node of AST.
QModelIndex selectNode(AstViewNode *node)
Programmatically select node and get its model index.
void nodeSelected(AstViewNode *node)
Emitted when node is selected.
void setEmptyMessage(const QString &message)
Message shown when the model has no AST loaded.
Represents node in AST tree hierarchy.
Immutable string with automatic deduplication via global pool.
const std::string & str() const
Get the underlying string value.