1
0
Fork 0

Merge pull request #114921 from LucaTuerk/quick_open_validate_history

EditorQuickOpenDialog: Rewrite internal paths usage and clean history
This commit is contained in:
Thaddeus Crews 2026-01-26 13:14:31 -06:00
commit 2d41087c8f
No known key found for this signature in database
GPG Key ID: 8C6E5FEB5FC03CCC
4 changed files with 221 additions and 112 deletions

View File

@ -129,6 +129,11 @@ String EditorFileSystemDirectory::get_file_path(int p_idx) const {
return get_path().path_join(get_file(p_idx));
}
ResourceUID::ID EditorFileSystemDirectory::get_file_uid(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, files.size(), ResourceUID::INVALID_ID);
return files[p_idx]->uid;
}
Vector<String> EditorFileSystemDirectory::get_file_deps(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, files.size(), Vector<String>());
Vector<String> deps;

View File

@ -91,6 +91,7 @@ public:
int get_file_count() const;
String get_file(int p_idx) const;
String get_file_path(int p_idx) const;
ResourceUID::ID get_file_uid(int p_idx) const;
StringName get_file_type(int p_idx) const;
StringName get_file_resource_script_class(int p_idx) const;
Vector<String> get_file_deps(int p_idx) const;

View File

@ -230,8 +230,11 @@ void EditorQuickOpenDialog::item_pressed(bool p_double_click) {
}
void EditorQuickOpenDialog::preview_property() {
Ref<Resource> loaded_resource = ResourceLoader::load(container->get_selected());
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + container->get_selected() + "'.");
ERR_FAIL_COND(container->get_selected() == ResourceUID::INVALID_ID);
String path = container->get_selected_path();
Ref<Resource> loaded_resource = ResourceLoader::load(path);
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + path + "'.");
Resource *res = Object::cast_to<Resource>(property_object);
if (res) {
@ -274,7 +277,7 @@ void EditorQuickOpenDialog::update_property() {
ERR_FAIL_MSG(err_msg);
}
item_selected_callback.call(container->get_selected());
item_selected_callback.call(container->get_selected_path());
}
void EditorQuickOpenDialog::cancel_pressed() {
@ -417,12 +420,15 @@ QuickOpenResultContainer::QuickOpenResultContainer() {
}
void QuickOpenResultContainer::_menu_option(int p_option) {
ERR_FAIL_COND(get_selected() == ResourceUID::INVALID_ID);
String selected_path = get_selected_path();
switch (p_option) {
case FILE_SHOW_IN_FILESYSTEM: {
FileSystemDock::get_singleton()->navigate_to_path(get_selected());
FileSystemDock::get_singleton()->navigate_to_path(selected_path);
} break;
case FILE_SHOW_IN_FILE_MANAGER: {
String dir = ProjectSettings::get_singleton()->globalize_path(get_selected());
String dir = ProjectSettings::get_singleton()->globalize_path(selected_path);
OS::get_singleton()->shell_show_in_file_manager(dir, true);
} break;
}
@ -473,111 +479,146 @@ void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
E->enable_highlights = enable_highlights;
}
bool history_modified = false;
if (first_open && history_file->load(_get_cache_file_path()) == OK) {
// Load history when opening for the first time.
file_type_icons.insert(SNAME("__default_icon"), get_editor_theme_icon(SNAME("Object")));
bool history_modified = false;
Vector<String> history_keys = history_file->get_section_keys("selected_history");
for (const String &type : history_keys) {
const StringName type_name = type;
const PackedStringArray paths = history_file->get_value("selected_history", type);
const PackedStringArray history_uids = history_file->get_value("selected_history", type);
PackedStringArray cleaned_paths;
cleaned_paths.resize(paths.size());
PackedStringArray cleaned_text_uids;
cleaned_text_uids.resize(history_uids.size());
Vector<ResourceUID::ID> cleaned_ids;
cleaned_ids.resize(history_uids.size());
Vector<QuickOpenResultCandidate> loaded_candidates;
loaded_candidates.resize(paths.size());
{
QuickOpenResultCandidate *candidates_write = loaded_candidates.ptrw();
String *cleanup_write = cleaned_paths.ptrw();
String *text_write = cleaned_text_uids.ptrw();
ResourceUID::ID *id_write = cleaned_ids.ptrw();
int i = 0;
for (String path : paths) {
if (path.begins_with("uid://")) {
ResourceUID::ID id = ResourceUID::get_singleton()->text_to_id(path);
if (!ResourceUID::get_singleton()->has_id(id)) {
for (String uid : history_uids) {
#ifndef DISABLE_DEPRECATED
if (!uid.begins_with("uid://")) {
// uid might be a path here, if config was written by older editor version
ResourceUID::ID id = EditorFileSystem::get_singleton()->get_file_uid(uid);
if (id == ResourceUID::INVALID_ID) {
continue;
}
path = ResourceUID::get_singleton()->get_id_path(id);
uid = ResourceUID::get_singleton()->id_to_text(id);
}
#endif
if (!ResourceLoader::exists(path)) {
ResourceUID::ID id = ResourceUID::get_singleton()->text_to_id(uid);
if (id == ResourceUID::INVALID_ID || !ResourceUID::get_singleton()->has_id(id)) {
continue;
}
filetypes.insert(path, type_name);
QuickOpenResultCandidate candidate;
_setup_candidate(candidate, path);
candidates_write[i] = candidate;
cleanup_write[i] = path;
filetypes.insert(id, type_name);
text_write[i] = uid;
id_write[i] = id;
i++;
}
loaded_candidates.resize(i);
cleaned_paths.resize(i);
selected_history.insert(type, loaded_candidates);
if (i < paths.size()) {
cleaned_text_uids.resize(i);
selected_history.insert(type, cleaned_ids);
if (i < history_uids.size()) {
// Some paths removed, need to update history.
if (i == 0) {
history_file->erase_section_key("selected_history", type);
} else {
history_file->set_value("selected_history", type, cleaned_paths);
history_file->set_value("selected_history", type, cleaned_text_uids);
}
history_modified = true;
}
}
}
if (history_modified) {
history_file->save(_get_cache_file_path());
} else if (!first_open && base_types.size() == 1) {
const StringName &type = base_types[0];
Vector<ResourceUID::ID> *history = selected_history.getptr(type);
if (history) {
Vector<ResourceUID::ID> clean_history;
for (const ResourceUID::ID &uid : *history) {
if (ResourceUID::get_singleton()->has_id(uid)) {
clean_history.push_back(uid);
} else {
history_modified = true;
}
}
if (clean_history.is_empty()) {
selected_history.erase(type);
} else if (history_modified) {
*history = clean_history;
}
}
}
if (history_modified) {
history_file->save(_get_cache_file_path());
}
_create_initial_results();
}
void QuickOpenResultContainer::_sort_filepaths(int p_max_results) {
void QuickOpenResultContainer::_sort_uids(int p_max_results) {
struct FilepathComparator {
bool operator()(const String &p_lhs, const String &p_rhs) const {
bool operator()(const ResourceUID::ID &p_lhs, const ResourceUID::ID &p_rhs) const {
String lhs_path = ResourceUID::get_singleton()->get_id_path(p_lhs);
String rhs_path = ResourceUID::get_singleton()->get_id_path(p_rhs);
// Sort on (length, alphanumeric) to prioritize shorter filepaths
return p_lhs.length() == p_rhs.length() ? p_lhs < p_rhs : p_lhs.length() < p_rhs.length();
return lhs_path.length() == rhs_path.length() ? lhs_path < rhs_path : lhs_path.length() < rhs_path.length();
}
};
SortArray<String, FilepathComparator> sorter;
if (filepaths.size() > p_max_results) {
sorter.partial_sort(0, filepaths.size(), p_max_results, filepaths.ptrw());
SortArray<ResourceUID::ID, FilepathComparator> sorter{};
if ((int)uids.size() > p_max_results) {
sorter.partial_sort(0, uids.size(), p_max_results, uids.ptr());
} else {
sorter.sort(filepaths.ptrw(), filepaths.size());
sorter.sort(uids.ptr(), uids.size());
}
}
void QuickOpenResultContainer::_create_initial_results() {
file_type_icons.clear();
file_type_icons.insert(SNAME("__default_icon"), get_editor_theme_icon(SNAME("Object")));
filepaths.clear();
uids.clear();
filetypes.clear();
history_set.clear();
Vector<QuickOpenResultCandidate> *history = _get_history();
Vector<ResourceUID::ID> *history = _get_history();
if (history) {
for (const QuickOpenResultCandidate &candidate : *history) {
history_set.insert(candidate.file_path);
for (const ResourceUID::ID &uid : *history) {
history_set.insert(uid);
}
}
_find_filepaths_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), include_addons_toggle->is_pressed());
_sort_filepaths(result_items.size());
max_total_results = MIN(filepaths.size(), result_items.size());
_find_uids_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), include_addons_toggle->is_pressed());
_sort_uids(result_items.size());
max_total_results = MIN(uids.size(), result_items.size());
update_results();
}
void QuickOpenResultContainer::_find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
void QuickOpenResultContainer::_find_uids_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
for (int i = 0; i < p_directory->get_subdir_count(); i++) {
if (p_include_addons || p_directory->get_name() != "addons") {
_find_filepaths_in_folder(p_directory->get_subdir(i), p_include_addons);
_find_uids_in_folder(p_directory->get_subdir(i), p_include_addons);
}
}
for (int i = 0; i < p_directory->get_file_count(); i++) {
String file_path = p_directory->get_file_path(i);
ResourceUID::ID uid = p_directory->get_file_uid(i);
if (uid == ResourceUID::INVALID_ID) {
continue;
}
const StringName engine_type = p_directory->get_file_type(i);
const StringName script_type = p_directory->get_file_resource_script_class(i);
@ -589,8 +630,8 @@ void QuickOpenResultContainer::_find_filepaths_in_folder(EditorFileSystemDirecto
bool is_valid = ClassDB::is_parent_class(engine_type, parent_type) || (!is_engine_type && EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type));
if (is_valid) {
filepaths.append(file_path);
filetypes.insert(file_path, actual_type);
uids.push_back(uid);
filetypes.insert(uid, actual_type);
break; // Stop testing base types as soon as we get a match.
}
}
@ -602,26 +643,53 @@ void QuickOpenResultContainer::set_query_and_update(const String &p_query) {
update_results();
}
Vector<QuickOpenResultCandidate> *QuickOpenResultContainer::_get_history() {
Vector<ResourceUID::ID> *QuickOpenResultContainer::_get_history() {
if (base_types.size() == 1) {
return selected_history.getptr(base_types[0]);
}
return nullptr;
}
void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath) {
p_candidate.file_path = ResourceUID::ensure_path(p_filepath);
p_candidate.result = nullptr;
QuickOpenResultCandidate QuickOpenResultCandidate::from_uid(const ResourceUID::ID &p_uid, bool &r_success) {
if (p_uid == ResourceUID::INVALID_ID || !ResourceUID::get_singleton()->has_id(p_uid)) {
r_success = false;
return QuickOpenResultCandidate();
}
QuickOpenResultCandidate candidate;
candidate.uid = p_uid;
candidate.result = nullptr;
r_success = true;
return candidate;
}
QuickOpenResultCandidate QuickOpenResultCandidate::from_result(const FuzzySearchResult &p_result, bool &r_success) {
ResourceUID::ID uid = EditorFileSystem::get_singleton()->get_file_uid(p_result.target);
QuickOpenResultCandidate candidate = from_uid(uid, r_success);
if (!r_success) {
return QuickOpenResultCandidate();
}
candidate.result = &p_result;
return candidate;
}
void QuickOpenResultContainer::_add_candidate(QuickOpenResultCandidate &p_candidate) {
ERR_FAIL_COND(!ResourceUID::get_singleton()->has_id(p_candidate.uid));
StringName actual_type;
{
StringName *actual_type_ptr = filetypes.getptr(p_filepath);
StringName *actual_type_ptr = filetypes.getptr(p_candidate.uid);
if (actual_type_ptr) {
actual_type = *actual_type_ptr;
} else {
ERR_PRINT(vformat("EditorQuickOpenDialog: No type for path %s.", p_candidate.file_path));
ERR_PRINT(vformat("EditorQuickOpenDialog: No type for path %s.", ResourceUID::get_singleton()->get_id_path(p_candidate.uid)));
}
}
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(p_candidate.file_path);
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(file_path);
if (item.preview.is_valid()) {
p_candidate.thumbnail = item.preview;
} else if (file_type_icons.has(actual_type)) {
@ -632,46 +700,54 @@ void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &p_cand
} else {
p_candidate.thumbnail = *file_type_icons.getptr(SNAME("__default_icon"));
}
}
void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result) {
_setup_candidate(p_candidate, p_result.target);
p_candidate.result = &p_result;
candidates.push_back(p_candidate);
candidates_uids.insert(p_candidate.uid);
}
void QuickOpenResultContainer::update_results() {
candidates.clear();
candidates_uids.clear();
if (query.is_empty()) {
_use_default_candidates();
} else {
_score_and_sort_candidates();
}
_update_result_items(MIN(candidates.size(), max_total_results), 0);
}
void QuickOpenResultContainer::_use_default_candidates() {
HashSet<String> existing_paths;
Vector<QuickOpenResultCandidate> *history = _get_history();
HashSet<ResourceUID::ID> existing_uids;
Vector<ResourceUID::ID> *history = _get_history();
if (history) {
candidates.append_array(*history);
for (const QuickOpenResultCandidate &candi : *history) {
existing_paths.insert(candi.file_path);
for (const ResourceUID::ID &uid : *history) {
bool success;
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_uid(uid, success);
if (!success) {
continue;
}
_add_candidate(candidate);
}
}
int i = candidates.size();
candidates.resize(MIN(max_total_results, filepaths.size()));
QuickOpenResultCandidate *candidates_w = candidates.ptrw();
int count = candidates.size();
for (const String &filepath : filepaths) {
if (i >= count) {
for (const ResourceUID::ID &uid : uids) {
if (candidates.size() >= max_total_results) {
break;
}
if (existing_paths.has(filepath)) {
if (candidates_uids.has(uid)) {
continue;
}
_setup_candidate(candidates_w[i++], filepath);
bool success;
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_uid(uid, success);
if (!success) {
continue;
}
_add_candidate(candidate);
}
}
@ -684,15 +760,28 @@ void QuickOpenResultContainer::_update_fuzzy_search_results() {
int max_misses = EDITOR_GET("filesystem/quick_open_dialog/max_fuzzy_misses");
fuzzy_search.allow_subsequences = fuzzy_matching;
fuzzy_search.max_misses = fuzzy_matching ? max_misses : 0;
fuzzy_search.search_all(filepaths, search_results);
PackedStringArray paths;
paths.reserve_exact(uids.size());
for (const ResourceUID::ID &uid : uids) {
paths.push_back(ResourceUID::get_singleton()->get_id_path(uid));
}
fuzzy_search.search_all(paths, search_results);
}
void QuickOpenResultContainer::_score_and_sort_candidates() {
_update_fuzzy_search_results();
candidates.resize(search_results.size());
QuickOpenResultCandidate *candidates_write = candidates.ptrw();
for (const FuzzySearchResult &result : search_results) {
_setup_candidate(*candidates_write++, result);
bool success;
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_result(result, success);
if (!success) {
continue;
}
_add_candidate(candidate);
}
}
@ -718,7 +807,7 @@ void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_co
no_results_container->set_visible(!any_results);
if (!any_results) {
if (filepaths.is_empty()) {
if (uids.is_empty()) {
no_results_label->set_text(TTR("No files found for this type"));
} else {
no_results_label->set_text(TTR("No results found"));
@ -815,8 +904,8 @@ void QuickOpenResultContainer::_select_item(int p_index) {
}
result_items[selection_index]->highlight_item(true);
bool in_history = history_set.has(candidates[selection_index].file_path);
file_details_path->set_text(get_selected() + (in_history ? TTR(" (recently opened)") : ""));
bool in_history = history_set.has(candidates[selection_index].uid);
file_details_path->set_text(get_selected_path() + (in_history ? TTR(" (recently opened)") : ""));
emit_signal(SNAME("selection_changed"));
@ -926,9 +1015,16 @@ bool QuickOpenResultContainer::has_nothing_selected() const {
return selection_index < 0;
}
String QuickOpenResultContainer::get_selected() const {
ERR_FAIL_COND_V_MSG(has_nothing_selected(), String(), "Tried to get selected file, but nothing was selected.");
return candidates[selection_index].file_path;
ResourceUID::ID QuickOpenResultContainer::get_selected() const {
ERR_FAIL_COND_V_MSG(has_nothing_selected(), ResourceUID::INVALID_ID, "Tried to get selected file, but nothing was selected.");
return candidates[selection_index].uid;
}
String QuickOpenResultContainer::get_selected_path() const {
ERR_FAIL_COND_V_MSG(has_nothing_selected(), "", "Tried to get selected file path, but nothing was selected.");
String path = ResourceUID::get_singleton()->get_id_path(candidates[selection_index].uid);
ERR_FAIL_COND_V_MSG(path.is_empty(), "", "Failed to get selected file path.");
return path;
}
QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
@ -971,40 +1067,39 @@ void QuickOpenResultContainer::save_selected_item() {
}
const StringName &base_type = base_types[0];
QuickOpenResultCandidate &selected = candidates.write[selection_index];
Vector<QuickOpenResultCandidate> *type_history = selected_history.getptr(base_type);
ResourceUID::ID selected = get_selected();
Vector<ResourceUID::ID> *type_history = selected_history.getptr(base_type);
if (!type_history) {
selected_history.insert(base_type, Vector<QuickOpenResultCandidate>());
selected_history.insert(base_type, Vector<ResourceUID::ID>());
type_history = selected_history.getptr(base_type);
} else {
for (int i = 0; i < type_history->size(); i++) {
if (selected.file_path == type_history->get(i).file_path) {
if (selected == type_history->get(i)) {
type_history->remove_at(i);
break;
}
}
}
selected.result = nullptr;
history_set.insert(selected.file_path);
history_set.insert(selected);
type_history->insert(0, selected);
if (type_history->size() > MAX_HISTORY_SIZE) {
type_history->resize(MAX_HISTORY_SIZE);
}
PackedStringArray paths;
paths.resize(type_history->size());
PackedStringArray history_uids;
history_uids.resize(type_history->size());
{
String *paths_write = paths.ptrw();
String *uids_write = history_uids.ptrw();
int i = 0;
for (const QuickOpenResultCandidate &candidate : *type_history) {
paths_write[i] = _get_uid_string(candidate.file_path);
for (const ResourceUID::ID &uid : *type_history) {
uids_write[i] = ResourceUID::get_singleton()->id_to_text(uid);
i++;
}
}
history_file->set_value("selected_history", base_type, paths);
history_file->set_value("selected_history", base_type, history_uids);
history_file->save(_get_cache_file_path());
}
@ -1200,8 +1295,10 @@ QuickOpenResultListItem::QuickOpenResultListItem() {
void QuickOpenResultListItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
thumbnail->set_texture(p_candidate.thumbnail);
name->set_text(p_candidate.file_path.get_file());
path->set_text(p_candidate.file_path.get_base_dir());
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
name->set_text(file_path.get_file());
path->set_text(file_path.get_base_dir());
name->reset_highlights();
path->reset_highlights();
@ -1271,8 +1368,10 @@ QuickOpenResultGridItem::QuickOpenResultGridItem() {
void QuickOpenResultGridItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
thumbnail->set_texture(p_candidate.thumbnail);
name->set_text(p_candidate.file_path.get_file());
name->set_tooltip_text(p_candidate.file_path);
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
name->set_text(file_path.get_file());
name->set_tooltip_text(file_path);
name->reset_highlights();
if (p_highlight && p_candidate.result != nullptr) {

View File

@ -60,9 +60,12 @@ enum class QuickOpenDisplayMode {
};
struct QuickOpenResultCandidate {
String file_path;
ResourceUID::ID uid;
Ref<Texture2D> thumbnail;
const FuzzySearchResult *result = nullptr;
static QuickOpenResultCandidate from_uid(const ResourceUID::ID &p_uid, bool &r_success);
static QuickOpenResultCandidate from_result(const FuzzySearchResult &p_result, bool &r_success);
};
class HighlightedLabel : public Label {
@ -95,7 +98,8 @@ public:
void update_results();
bool has_nothing_selected() const;
String get_selected() const;
ResourceUID::ID get_selected() const;
String get_selected_path() const;
bool is_instant_preview_enabled() const;
void set_instant_preview_toggle_visible(bool p_visible);
@ -113,12 +117,13 @@ private:
Vector<FuzzySearchResult> search_results;
Vector<StringName> base_types;
Vector<String> filepaths;
AHashMap<String, StringName> filetypes;
LocalVector<ResourceUID::ID> uids;
AHashMap<ResourceUID::ID, StringName> filetypes;
Vector<QuickOpenResultCandidate> candidates;
HashSet<ResourceUID::ID> candidates_uids;
AHashMap<StringName, Vector<QuickOpenResultCandidate>> selected_history;
HashSet<String> history_set;
AHashMap<StringName, Vector<ResourceUID::ID>> selected_history;
HashSet<ResourceUID::ID> history_set;
String query;
int selection_index = -1;
@ -151,13 +156,12 @@ private:
static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
void _ensure_result_vector_capacity();
void _sort_filepaths(int p_max_results);
void _sort_uids(int p_max_results);
void _create_initial_results();
void _find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
void _find_uids_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
Vector<QuickOpenResultCandidate> *_get_history();
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath);
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result);
Vector<ResourceUID::ID> *_get_history();
void _add_candidate(QuickOpenResultCandidate &p_candidate);
void _update_fuzzy_search_results();
void _use_default_candidates();
void _score_and_sort_candidates();