serenity/Userland/DevTools/HackStudio/ClassViewWidget.cpp
Itamar c1b2003687 HackStudio: Use Node's name when inserting to the ClassView tree
Previously, when traversing the ClassView tree to find the parent of a
new node, we used the name of the node's declaration to find the path
to the parent in the tree.

However, some nodes in the tree do not have a matching declaration,
which caused a VERIFY failure.

To fix this, we now use the node's name when walking the tree.
We can do this because the node's name should be identical to the name
of its declaration.

Closes #7702.
2021-06-04 19:29:22 +02:00

192 lines
5.8 KiB
C++

/*
* Copyright (c) 2021, Itamar S. <itamar8910@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ClassViewWidget.h"
#include "HackStudio.h"
#include "ProjectDeclarations.h"
#include <AK/StdLibExtras.h>
#include <LibGUI/BoxLayout.h>
#include <string.h>
namespace HackStudio {
ClassViewWidget::ClassViewWidget()
{
set_layout<GUI::VerticalBoxLayout>();
m_class_tree = add<GUI::TreeView>();
m_class_tree->on_selection_change = [this] {
const auto& index = m_class_tree->selection().first();
if (!index.is_valid())
return;
auto* node = static_cast<const ClassViewNode*>(index.internal_data());
if (!node->declaration)
return;
open_file(node->declaration->position.file, node->declaration->position.line, node->declaration->position.column);
};
}
RefPtr<ClassViewModel> ClassViewModel::create()
{
return adopt_ref(*new ClassViewModel());
}
int ClassViewModel::row_count(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return m_root_scope.size();
auto* node = static_cast<ClassViewNode*>(index.internal_data());
return node->children.size();
}
GUI::Variant ClassViewModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
{
auto* node = static_cast<const ClassViewNode*>(index.internal_data());
switch (role) {
case GUI::ModelRole::Display: {
return node->name;
}
case GUI::ModelRole::Icon: {
if (!node->declaration)
return {};
auto icon = ProjectDeclarations::get_icon_for(node->declaration->type);
if (icon.has_value())
return icon.value();
return {};
}
default:
return {};
}
}
GUI::ModelIndex ClassViewModel::parent_index(const GUI::ModelIndex& index) const
{
if (!index.is_valid())
return {};
auto* child = static_cast<const ClassViewNode*>(index.internal_data());
auto* parent = child->parent;
if (parent == nullptr)
return {};
if (parent->parent == nullptr) {
for (size_t row = 0; row < m_root_scope.size(); row++) {
if (m_root_scope.ptr_at(row).ptr() == parent)
return create_index(row, 0, parent);
}
VERIFY_NOT_REACHED();
}
for (size_t row = 0; row < parent->parent->children.size(); row++) {
ClassViewNode* child_at_row = parent->parent->children.ptr_at(row).ptr();
if (child_at_row == parent)
return create_index(row, 0, parent);
}
VERIFY_NOT_REACHED();
}
GUI::ModelIndex ClassViewModel::index(int row, int column, const GUI::ModelIndex& parent_index) const
{
if (!parent_index.is_valid())
return create_index(row, column, &m_root_scope[row]);
auto* parent = static_cast<const ClassViewNode*>(parent_index.internal_data());
auto* child = &parent->children[row];
return create_index(row, column, child);
}
ClassViewModel::ClassViewModel()
{
m_root_scope.clear();
ProjectDeclarations::the().for_each_declared_symbol([this](auto& decl) {
if (decl.type == GUI::AutocompleteProvider::DeclarationType::Class
|| decl.type == GUI::AutocompleteProvider::DeclarationType::Struct
|| decl.type == GUI::AutocompleteProvider::DeclarationType::Member
|| decl.type == GUI::AutocompleteProvider::DeclarationType::Namespace) {
add_declaration(decl);
}
});
}
static ClassViewNode& add_child_node(NonnullOwnPtrVector<ClassViewNode>& children, NonnullOwnPtr<ClassViewNode>&& node_ptr, ClassViewNode* parent, const GUI::AutocompleteProvider::Declaration* declaration)
{
node_ptr->parent = parent;
node_ptr->declaration = declaration;
size_t inserted_index = 0;
ClassViewNode& node = *node_ptr;
children.insert_before_matching(
move(node_ptr), [&node](auto& other_node) {
return strncmp(node.name.characters_without_null_termination(), other_node->name.characters_without_null_termination(), min(node.name.length(), other_node->name.length())) < 0;
},
0, &inserted_index);
return children.at(inserted_index);
}
void ClassViewModel::add_declaration(const GUI::AutocompleteProvider::Declaration& decl)
{
ClassViewNode* parent = nullptr;
auto scope_parts = decl.scope.view().split_view("::");
if (!scope_parts.is_empty()) {
// Traverse declarations tree to the parent of 'decl'
for (auto& node : m_root_scope) {
if (node.name == scope_parts.first())
parent = &node;
}
if (parent == nullptr) {
m_root_scope.append(make<ClassViewNode>(scope_parts.first()));
parent = &m_root_scope.last();
}
for (size_t i = 1; i < scope_parts.size(); ++i) {
auto& scope = scope_parts[i];
ClassViewNode* next { nullptr };
for (auto& child : parent->children) {
if (child.name == scope) {
next = &child;
break;
}
}
if (next) {
parent = next;
continue;
}
parent = &add_child_node(parent->children, make<ClassViewNode>(scope), parent, nullptr);
}
}
NonnullOwnPtrVector<ClassViewNode>* children_of_parent = nullptr;
if (parent) {
children_of_parent = &parent->children;
} else {
children_of_parent = &m_root_scope;
}
bool already_exists = false;
for (auto& child : *children_of_parent) {
if (child.name == decl.name) {
already_exists = true;
if (!child.declaration) {
child.declaration = &decl;
}
break;
}
}
if (!already_exists) {
add_child_node(*children_of_parent, make<ClassViewNode>(decl.name), parent, &decl);
}
}
void ClassViewWidget::refresh()
{
m_class_tree->set_model(ClassViewModel::create());
}
}