Слияние ветки 'fix/persist-user-project-name' в 'releases/openide/253.28294.334'

This commit is contained in:
Nikita Iarychenko
2026-08-05 10:07:04 +03:00
committed by GitFlic
4 changed files with 100 additions and 4 deletions
@@ -1,4 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
//
// Modified by Aleksandr Eliseev at 2026 as part of the OpenIDE project(https://openide.ru).
// Any modifications are available on the same license terms as the original source code.
package com.intellij.openapi.roots.ui.configuration;
@@ -28,8 +31,10 @@ import com.intellij.project.ProjectKt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.openide.project.ProjectNameCustomizationState;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
/**
@@ -149,7 +154,15 @@ public class ProjectConfigurable extends ProjectStructureElementConfigurable<Pro
myUi.applyProjectJdkConfigurable();
if (myProject instanceof ProjectEx) {
((ProjectEx)myProject).setProjectName(getProjectName());
ProjectEx projectEx = (ProjectEx)myProject;
String newName = getProjectName();
projectEx.setProjectName(newName);
// Record whether the user explicitly customized the name.
// An external system (e.g. Gradle) sync must not overwrite a customized name;
// resetting it to the directory name clears the flag so the sync can drive it again.
ProjectNameCustomizationState.Companion.getInstance(myProject).setUserCustomizedName(
isCustomProjectName(myProject, newName)
);
if (myDetailsComponent != null) myDetailsComponent.setText(getBannerSlogan());
}
});
@@ -211,6 +224,17 @@ public class ProjectConfigurable extends ProjectStructureElementConfigurable<Pro
return myProject.getName();
}
/**
* Returns true if the given name is a user customization, i.e. it differs from the project's
* directory name (the default project name). Used to decide whether an external system sync may
* override the project name.
*/
private static boolean isCustomProjectName(@NotNull Project project, @NotNull String name) {
String basePath = project.getBasePath();
if (basePath == null) return true;
return !name.equals(new File(basePath).getName());
}
private @Nullable String getOriginalCompilerOutputUrl() {
final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(myProject);
return extension != null ? extension.getCompilerOutputUrl() : null;
@@ -1,4 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
//
// Modified by Aleksandr Eliseev at 2026 as part of the OpenIDE project(https://openide.ru).
// Any modifications are available on the same license terms as the original source code.
package com.intellij.openapi.externalSystem.service.project.manage;
import com.intellij.openapi.externalSystem.model.DataNode;
@@ -10,6 +13,7 @@ import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsPr
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.externalSystem.util.Order;
import ru.openide.project.ProjectNameCustomizationState;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectEx;
import org.jetbrains.annotations.ApiStatus;
@@ -45,9 +49,14 @@ public final class ProjectDataServiceImpl extends AbstractProjectDataService<Pro
if (!ExternalSystemApiUtil.isOneToOneMapping(project, node.getData(), modelsProvider.getModules())) {
return;
}
if (!project.getName().equals(projectData.getInternalName())) {
renameProject(projectData.getInternalName(), projectData.getOwner(), project);
// Don't overwrite a project name the user has explicitly customized (via File | Project
// Structure or Refactor | Rename) when the external (e.g. Gradle) root project name differs.
// A Gradle-driven rename (no user customization) still propagates, including across multiple syncs.
if (!ProjectNameCustomizationState.Companion.getInstance(project).getUserCustomizedName()) {
renameProject(projectData.getInternalName(), projectData.getOwner(), project);
}
}
}
@@ -1,4 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
//
// Modified by Aleksandr Eliseev at 2026 as part of the OpenIDE project(https://openide.ru).
// Any modifications are available on the same license terms as the original source code.
package com.intellij.platform.renameProject;
import com.intellij.ide.IdeBundle;
@@ -24,6 +27,9 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.RenameHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.openide.project.ProjectNameCustomizationState;
import java.io.File;
public final class RenameProjectHandler implements RenameHandler, TitledHandler {
private static final Logger LOG = Logger.getInstance(RenameProjectHandler.class);
@@ -84,6 +90,13 @@ public final class RenameProjectHandler implements RenameHandler, TitledHandler
public static boolean renameProject(@NotNull ProjectEx project, @Nullable Module module, String newName){
if (shouldRenameProject(project, module, newName)) {
project.setProjectName(newName);
// The user explicitly renamed the project from Refactor | Rename. Record it as a
// customization so an external system (e.g. Gradle) sync doesn't overwrite it; resetting the
// name to the directory name clears it and lets the sync drive the name again.
String basePath = project.getBasePath();
ProjectNameCustomizationState.Companion.getInstance(project).setUserCustomizedName(
basePath == null || !newName.equals(new File(basePath).getName())
);
project.save();
}
@@ -121,4 +134,4 @@ public final class RenameProjectHandler implements RenameHandler, TitledHandler
return module == PrimaryModuleManager.findPrimaryModule(project);
}
}
}
@@ -0,0 +1,50 @@
// OpenIDE Project
// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
package ru.openide.project
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.StoragePathMacros
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.XmlSerializerUtil
/**
* Tracks whether the user has explicitly customized the IDE project name (via File | Project
* Structure or Refactor | Rename on the project), as opposed to the name being derived from the
* project directory or set by an external system (e.g. Gradle) sync.
*
* An external system sync that wants to rename the project to match the build model must respect
* this flag: once the user has chosen a custom name, the sync must not overwrite it. The flag is
* cleared again when the user resets the name to the project directory name, so an external system
* can resume driving the name.
*/
@Service(Service.Level.PROJECT)
@State(name = "ProjectNameCustomizationState", storages = [Storage(StoragePathMacros.WORKSPACE_FILE)])
class ProjectNameCustomizationState : PersistentStateComponent<ProjectNameCustomizationState> {
companion object {
fun getInstance(project: Project): ProjectNameCustomizationState =
project.getService(ProjectNameCustomizationState::class.java)
}
var userCustomizedName: Boolean = false
override fun getState(): ProjectNameCustomizationState = this
override fun loadState(state: ProjectNameCustomizationState) {
XmlSerializerUtil.copyBean(state, this)
}
}