implement generic filename disambiguation

A static function is added to EditorNode which allows for filename
disambiguation given a list of filenames and the corresponding list of
absolute paths for those files. This function is then used to
disambiguate scene and script tabs in the editor.
This commit is contained in:
rileylyman 2020-05-26 21:49:05 -07:00 committed by rileylyman
parent bcf7e51689
commit 4285211f40
3 changed files with 131 additions and 1 deletions

View file

@ -178,6 +178,111 @@
EditorNode *EditorNode::singleton = nullptr;
void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) {
// Keep track of a list of "index sets," i.e. sets of indices
// within disambiguated_scene_names which contain the same name.
Vector<Set<int>> index_sets;
Map<String, int> scene_name_to_set_index;
for (int i = 0; i < r_filenames.size(); i++) {
String scene_name = r_filenames[i];
if (!scene_name_to_set_index.has(scene_name)) {
index_sets.append(Set<int>());
scene_name_to_set_index.insert(r_filenames[i], index_sets.size() - 1);
}
index_sets.write[scene_name_to_set_index[scene_name]].insert(i);
}
// For each index set with a size > 1, we need to disambiguate
for (int i = 0; i < index_sets.size(); i++) {
Set<int> iset = index_sets[i];
while (iset.size() > 1) {
// Append the parent folder to each scene name
for (Set<int>::Element *E = iset.front(); E; E = E->next()) {
int set_idx = E->get();
String scene_name = r_filenames[set_idx];
String full_path = p_full_paths[set_idx];
// Get rid of file extensions and res:// prefixes
if (scene_name.rfind(".") >= 0) {
scene_name = scene_name.substr(0, scene_name.rfind("."));
}
if (full_path.begins_with("res://")) {
full_path = full_path.substr(6);
}
if (full_path.rfind(".") >= 0) {
full_path = full_path.substr(0, full_path.rfind("."));
}
int scene_name_size = scene_name.size();
int full_path_size = full_path.size();
int difference = full_path_size - scene_name_size;
// Find just the parent folder of the current path and append it.
// If the current name is foo.tscn, and the full path is /some/folder/foo.tscn
// then slash_idx is the second '/', so that we select just "folder", and
// append that to yield "folder/foo.tscn".
if (difference > 0) {
String parent = full_path.substr(0, difference);
int slash_idx = parent.rfind("/");
slash_idx = parent.rfind("/", slash_idx - 1);
parent = slash_idx >= 0 ? parent.substr(slash_idx + 1) : parent;
r_filenames.write[set_idx] = parent + r_filenames[set_idx];
}
}
// Loop back through scene names and remove non-ambiguous names
bool can_proceed = false;
Set<int>::Element *E = iset.front();
while (E) {
String scene_name = r_filenames[E->get()];
bool duplicate_found = false;
for (Set<int>::Element *F = iset.front(); F; F = F->next()) {
if (E->get() == F->get()) {
continue;
}
String other_scene_name = r_filenames[F->get()];
if (other_scene_name == scene_name) {
duplicate_found = true;
break;
}
}
Set<int>::Element *to_erase = duplicate_found ? nullptr : E;
// We need to check that we could actually append anymore names
// if we wanted to for disambiguation. If we can't, then we have
// to abort even with ambiguous names. We clean the full path
// and the scene name first to remove extensions so that this
// comparison actually works.
String path = p_full_paths[E->get()];
if (path.begins_with("res://")) {
path = path.substr(6);
}
if (path.rfind(".") >= 0) {
path = path.substr(0, path.rfind("."));
}
if (scene_name.rfind(".") >= 0) {
scene_name = scene_name.substr(0, scene_name.rfind("."));
}
// We can proceed iff the full path is longer than the scene name,
// meaning that there is at least one more parent folder we can
// tack onto the name.
can_proceed = can_proceed || (path.size() - scene_name.size()) >= 1;
E = E->next();
if (to_erase) {
iset.erase(to_erase);
}
}
if (!can_proceed) {
break;
}
}
}
}
void EditorNode::_update_scene_tabs() {
bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button");
@ -185,6 +290,16 @@ void EditorNode::_update_scene_tabs() {
DisplayServer::get_singleton()->global_menu_clear("_dock");
}
// Get all scene names, which may be ambiguous
Vector<String> disambiguated_scene_names;
Vector<String> full_path_names;
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
disambiguated_scene_names.append(editor_data.get_scene_title(i));
full_path_names.append(editor_data.get_scene_path(i));
}
disambiguate_filenames(full_path_names, disambiguated_scene_names);
scene_tabs->clear_tabs();
Ref<Texture2D> script_icon = gui_base->get_theme_icon("Script", "EditorIcons");
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
@ -196,7 +311,7 @@ void EditorNode::_update_scene_tabs() {
int current = editor_data.get_edited_scene();
bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0;
scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon);
scene_tabs->add_tab(disambiguated_scene_names[i] + (unsaved ? "(*)" : ""), icon);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
DisplayServer::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), callable_mp(this, &EditorNode::_global_menu_scene), i);

View file

@ -687,6 +687,8 @@ public:
static void add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed = false);
static void remove_editor_plugin(EditorPlugin *p_editor, bool p_config_changed = false);
static void disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames);
void new_inherited_scene() { _menu_option_confirm(FILE_NEW_INHERITED_SCENE, false); }
void set_docks_visible(bool p_show);

View file

@ -1731,6 +1731,19 @@ void ScriptEditor::_update_script_names() {
sedata.push_back(sd);
}
Vector<String> disambiguated_script_names;
Vector<String> full_script_paths;
for (int j = 0; j < sedata.size(); j++) {
disambiguated_script_names.append(sedata[j].name);
full_script_paths.append(sedata[j].tooltip);
}
EditorNode::disambiguate_filenames(full_script_paths, disambiguated_script_names);
for (int j = 0; j < sedata.size(); j++) {
sedata.write[j].name = disambiguated_script_names[j];
}
EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i));
if (eh) {
String name = eh->get_class();