mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-233558 force reapply project frame layout
GitOrigin-RevId: f5ecff5241f05e3c7291d73a534c4f8cee7516e5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
64f85ceb62
commit
afd381f6a2
+61
@@ -64,4 +64,65 @@ class ToolWindowLayoutProfileServiceTest {
|
||||
val result = service<ToolWindowLayoutProfileService>().getLayout(project = project, profileId = "missing", isNewUi = true)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun returnsProfileMetadataFromMatchingProvider() {
|
||||
val firstLayout = DesktopLayout()
|
||||
|
||||
ExtensionTestUtil.maskExtensions(
|
||||
ToolWindowLayoutProfileProvider.EP_NAME,
|
||||
listOf(
|
||||
object : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
return firstLayout.takeIf { profileId == "dedicated" }
|
||||
}
|
||||
|
||||
override fun getApplyMode(project: Project, profileId: String, isNewUi: Boolean): ToolWindowLayoutApplyMode {
|
||||
return ToolWindowLayoutApplyMode.FORCE_ONCE
|
||||
}
|
||||
|
||||
override fun getMigrationVersion(project: Project, profileId: String, isNewUi: Boolean): Int {
|
||||
return 7
|
||||
}
|
||||
},
|
||||
),
|
||||
disposable,
|
||||
)
|
||||
|
||||
val result = service<ToolWindowLayoutProfileService>().getProfile(project = project, profileId = "dedicated", isNewUi = true)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.layout).isSameAs(firstLayout)
|
||||
assertThat(result.applyMode).isEqualTo(ToolWindowLayoutApplyMode.FORCE_ONCE)
|
||||
assertThat(result.migrationVersion).isEqualTo(7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clampsNegativeMigrationVersionToZero() {
|
||||
val firstLayout = DesktopLayout()
|
||||
|
||||
ExtensionTestUtil.maskExtensions(
|
||||
ToolWindowLayoutProfileProvider.EP_NAME,
|
||||
listOf(
|
||||
object : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
return firstLayout.takeIf { profileId == "dedicated" }
|
||||
}
|
||||
|
||||
override fun getApplyMode(project: Project, profileId: String, isNewUi: Boolean): ToolWindowLayoutApplyMode {
|
||||
return ToolWindowLayoutApplyMode.FORCE_ONCE
|
||||
}
|
||||
|
||||
override fun getMigrationVersion(project: Project, profileId: String, isNewUi: Boolean): Int {
|
||||
return -1
|
||||
}
|
||||
},
|
||||
),
|
||||
disposable,
|
||||
)
|
||||
|
||||
val result = service<ToolWindowLayoutProfileService>().getProfile(project = project, profileId = "dedicated", isNewUi = true)
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.migrationVersion).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.wm.impl
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ex.ProjectFrameCapabilitiesService
|
||||
import com.intellij.toolWindow.ToolWindowLayoutApplyMode
|
||||
import com.intellij.toolWindow.ToolWindowLayoutProfile
|
||||
import com.intellij.toolWindow.ToolWindowLayoutProfileService
|
||||
|
||||
private const val TOOL_WINDOW_LAYOUT_MIGRATION_PROPERTY_PREFIX = "toolwindow.layout.profile.migration."
|
||||
|
||||
internal data class ProjectFrameToolWindowLayoutProfile(
|
||||
val profileId: String,
|
||||
val profile: ToolWindowLayoutProfile,
|
||||
)
|
||||
|
||||
internal fun resolveProjectFrameToolWindowLayoutProfile(
|
||||
project: Project,
|
||||
isNewUi: Boolean,
|
||||
): ProjectFrameToolWindowLayoutProfile? {
|
||||
val uiPolicy = service<ProjectFrameCapabilitiesService>().getUiPolicy(project) ?: return null
|
||||
val profileId = uiPolicy.toolWindowLayoutProfileId ?: return null
|
||||
val profile = service<ToolWindowLayoutProfileService>().getProfile(project = project, profileId = profileId, isNewUi = isNewUi)
|
||||
?: return null
|
||||
return ProjectFrameToolWindowLayoutProfile(profileId = profileId, profile = profile)
|
||||
}
|
||||
|
||||
internal fun applyProjectFrameLayoutPolicy(
|
||||
projectFrameLayoutProfile: ProjectFrameToolWindowLayoutProfile?,
|
||||
scheduleSetLayout: (DesktopLayout) -> Unit,
|
||||
) {
|
||||
val (profileId, profile) = projectFrameLayoutProfile ?: return
|
||||
when (profile.applyMode) {
|
||||
ToolWindowLayoutApplyMode.SEED_ONLY -> return
|
||||
ToolWindowLayoutApplyMode.FORCE_ONCE -> applyProjectFrameLayoutOnce(
|
||||
profileId = profileId,
|
||||
profile = profile,
|
||||
scheduleSetLayout = scheduleSetLayout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyProjectFrameLayoutOnce(
|
||||
profileId: String,
|
||||
profile: ToolWindowLayoutProfile,
|
||||
scheduleSetLayout: (DesktopLayout) -> Unit,
|
||||
) {
|
||||
val migrationVersion = profile.migrationVersion
|
||||
if (migrationVersion <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val propertiesComponent = PropertiesComponent.getInstance()
|
||||
val migrationKey = TOOL_WINDOW_LAYOUT_MIGRATION_PROPERTY_PREFIX + profileId
|
||||
val appliedMigrationVersion = propertiesComponent.getInt(migrationKey, 0)
|
||||
if (appliedMigrationVersion >= migrationVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleSetLayout(profile.layout)
|
||||
propertiesComponent.setValue(migrationKey, migrationVersion, 0)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment", "OverridingDeprecatedMember", "ReplaceNegatedIsEmptyWithIsNotEmpty",
|
||||
"PrivatePropertyName")
|
||||
@file:OptIn(FlowPreview::class)
|
||||
@@ -87,7 +87,6 @@ import com.intellij.openapi.wm.ToolWindowType
|
||||
import com.intellij.openapi.wm.WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
|
||||
import com.intellij.openapi.wm.WindowInfo
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.intellij.openapi.wm.ex.ProjectFrameCapabilitiesService
|
||||
import com.intellij.openapi.wm.ex.ToolWindowEx
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||
@@ -102,7 +101,6 @@ import com.intellij.toolWindow.ToolWindowButtonManager
|
||||
import com.intellij.toolWindow.ToolWindowDefaultLayoutManager
|
||||
import com.intellij.toolWindow.ToolWindowEntry
|
||||
import com.intellij.toolWindow.ToolWindowEventSource
|
||||
import com.intellij.toolWindow.ToolWindowLayoutProfileService
|
||||
import com.intellij.toolWindow.ToolWindowPane
|
||||
import com.intellij.toolWindow.ToolWindowPaneNewButtonManager
|
||||
import com.intellij.toolWindow.ToolWindowProperty
|
||||
@@ -244,14 +242,18 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
|
||||
delay = SystemProperties.getIntProperty("actionSystem.keyGestureDblClickTime", 300),
|
||||
coroutineScope = coroutineScope,
|
||||
)
|
||||
val projectFrameLayoutProfile = resolveProjectFrameToolWindowLayoutProfile(project = project, isNewUi = isNewUi)
|
||||
if (state.noStateLoaded) {
|
||||
loadDefault()
|
||||
loadDefault(projectFrameLayoutProfile)
|
||||
}
|
||||
@Suppress("LeakingThis")
|
||||
state.scheduledLayout.afterChange(this) { dl ->
|
||||
dl?.let { toolWindowSetInitializer.scheduleSetLayout(it) }
|
||||
}
|
||||
state.scheduledLayout.get()?.let { toolWindowSetInitializer.scheduleSetLayout(it) }
|
||||
applyProjectFrameLayoutPolicy(projectFrameLayoutProfile) { layout ->
|
||||
toolWindowSetInitializer.scheduleSetLayout(layout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,17 +777,16 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDefault() {
|
||||
val layout = getLayoutForProjectFrameProfile() ?: ToolWindowDefaultLayoutManager.getInstance().getLayoutCopy()
|
||||
private fun loadDefault(
|
||||
projectFrameLayoutProfile: ProjectFrameToolWindowLayoutProfile? = resolveProjectFrameToolWindowLayoutProfile(
|
||||
project = project,
|
||||
isNewUi = isNewUi,
|
||||
),
|
||||
) {
|
||||
val layout = projectFrameLayoutProfile?.profile?.layout ?: ToolWindowDefaultLayoutManager.getInstance().getLayoutCopy()
|
||||
toolWindowSetInitializer.scheduleSetLayout(layout)
|
||||
}
|
||||
|
||||
private fun getLayoutForProjectFrameProfile(): DesktopLayout? {
|
||||
val uiPolicy = service<ProjectFrameCapabilitiesService>().getUiPolicy(project) ?: return null
|
||||
val profileId = uiPolicy.toolWindowLayoutProfileId ?: return null
|
||||
return service<ToolWindowLayoutProfileService>().getLayout(project = project, profileId = profileId, isNewUi = isNewUi)
|
||||
}
|
||||
|
||||
@Deprecated("Use {@link ToolWindowManagerListener#TOPIC}", level = DeprecationLevel.ERROR)
|
||||
override fun addToolWindowManagerListener(listener: ToolWindowManagerListener) {
|
||||
dispatcher.addListener(listener)
|
||||
|
||||
+43
-7
@@ -23,12 +23,44 @@ interface ToolWindowLayoutProfileProvider {
|
||||
}
|
||||
|
||||
fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout?
|
||||
|
||||
fun getApplyMode(project: Project, profileId: String, isNewUi: Boolean): ToolWindowLayoutApplyMode {
|
||||
return ToolWindowLayoutApplyMode.SEED_ONLY
|
||||
}
|
||||
|
||||
fun getMigrationVersion(project: Project, profileId: String, isNewUi: Boolean): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
enum class ToolWindowLayoutApplyMode {
|
||||
/**
|
||||
* Apply only when no per-project layout was persisted yet.
|
||||
*/
|
||||
SEED_ONLY,
|
||||
|
||||
/**
|
||||
* Apply once for the given profile migration version.
|
||||
*/
|
||||
FORCE_ONCE,
|
||||
}
|
||||
|
||||
@Internal
|
||||
data class ToolWindowLayoutProfile(
|
||||
val layout: DesktopLayout,
|
||||
val applyMode: ToolWindowLayoutApplyMode,
|
||||
val migrationVersion: Int,
|
||||
)
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@Internal
|
||||
class ToolWindowLayoutProfileService {
|
||||
fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
return getProfile(project = project, profileId = profileId, isNewUi = isNewUi)?.layout
|
||||
}
|
||||
|
||||
fun getProfile(project: Project, profileId: String, isNewUi: Boolean): ToolWindowLayoutProfile? {
|
||||
if (project.isDisposed) {
|
||||
return null
|
||||
}
|
||||
@@ -38,20 +70,24 @@ class ToolWindowLayoutProfileService {
|
||||
return null
|
||||
}
|
||||
|
||||
var layout: DesktopLayout? = null
|
||||
var layoutProvider: ToolWindowLayoutProfileProvider? = null
|
||||
var profile: ToolWindowLayoutProfile? = null
|
||||
var profileProvider: ToolWindowLayoutProfileProvider? = null
|
||||
for (provider in providers) {
|
||||
try {
|
||||
val providerLayout = provider.getLayout(project = project, profileId = profileId, isNewUi = isNewUi)
|
||||
if (providerLayout != null) {
|
||||
if (layout == null) {
|
||||
layout = providerLayout
|
||||
layoutProvider = provider
|
||||
if (profile == null) {
|
||||
profile = ToolWindowLayoutProfile(
|
||||
layout = providerLayout,
|
||||
applyMode = provider.getApplyMode(project = project, profileId = profileId, isNewUi = isNewUi),
|
||||
migrationVersion = provider.getMigrationVersion(project = project, profileId = profileId, isNewUi = isNewUi).coerceAtLeast(0),
|
||||
)
|
||||
profileProvider = provider
|
||||
}
|
||||
else {
|
||||
LOG.error(
|
||||
"Multiple tool window layouts are provided for profile '$profileId'. " +
|
||||
"Keeping ${layoutProvider?.javaClass?.name}, ignoring ${provider.javaClass.name}."
|
||||
"Keeping ${profileProvider?.javaClass?.name}, ignoring ${provider.javaClass.name}."
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +100,7 @@ class ToolWindowLayoutProfileService {
|
||||
}
|
||||
}
|
||||
|
||||
return layout
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ package com.intellij.agent.workbench.sessions
|
||||
|
||||
internal const val AGENT_SESSIONS_TOOL_WINDOW_ID: String = "agent.workbench.sessions"
|
||||
internal const val AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID: String = "agent.workbench.dedicated"
|
||||
internal const val AGENT_WORKBENCH_LAYOUT_MIGRATION_VERSION: Int = 1
|
||||
|
||||
+19
-1
@@ -10,8 +10,8 @@ import com.intellij.openapi.wm.WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
|
||||
import com.intellij.openapi.wm.impl.DesktopLayout
|
||||
import com.intellij.openapi.wm.impl.WindowInfoImpl
|
||||
import com.intellij.toolWindow.ToolWindowDefaultLayoutManager
|
||||
import com.intellij.toolWindow.ToolWindowLayoutApplyMode
|
||||
import com.intellij.toolWindow.ToolWindowLayoutProfileProvider
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
|
||||
internal class AgentWorkbenchToolWindowLayoutProfileProvider : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
@@ -45,4 +45,22 @@ internal class AgentWorkbenchToolWindowLayoutProfileProvider : ToolWindowLayoutP
|
||||
|
||||
return DesktopLayout(infos, baseLayout.unifiedWeights.copy())
|
||||
}
|
||||
|
||||
override fun getApplyMode(project: Project, profileId: String, isNewUi: Boolean): ToolWindowLayoutApplyMode {
|
||||
return if (profileId == AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID) {
|
||||
ToolWindowLayoutApplyMode.FORCE_ONCE
|
||||
}
|
||||
else {
|
||||
ToolWindowLayoutApplyMode.SEED_ONLY
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMigrationVersion(project: Project, profileId: String, isNewUi: Boolean): Int {
|
||||
return if (profileId == AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID) {
|
||||
AGENT_WORKBENCH_LAYOUT_MIGRATION_VERSION
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.agent.workbench.sessions
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.toolWindow.ToolWindowLayoutApplyMode
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
class AgentWorkbenchToolWindowLayoutProfileProviderTest {
|
||||
private val provider = AgentWorkbenchToolWindowLayoutProfileProvider()
|
||||
|
||||
@Test
|
||||
fun dedicatedProfileUsesForceOnceMigrationPolicy() {
|
||||
val project = testProject()
|
||||
|
||||
assertThat(provider.getApplyMode(project, AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID, isNewUi = true))
|
||||
.isEqualTo(ToolWindowLayoutApplyMode.FORCE_ONCE)
|
||||
assertThat(provider.getMigrationVersion(project, AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID, isNewUi = true))
|
||||
.isEqualTo(AGENT_WORKBENCH_LAYOUT_MIGRATION_VERSION)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonDedicatedProfileKeepsSeedOnlyPolicy() {
|
||||
val project = testProject()
|
||||
|
||||
assertThat(provider.getApplyMode(project, "other.profile", isNewUi = true))
|
||||
.isEqualTo(ToolWindowLayoutApplyMode.SEED_ONLY)
|
||||
assertThat(provider.getMigrationVersion(project, "other.profile", isNewUi = true))
|
||||
.isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun testProject(): Project {
|
||||
val handler = java.lang.reflect.InvocationHandler { _, method, _ ->
|
||||
when (method.name) {
|
||||
"isDisposed" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
return Proxy.newProxyInstance(Project::class.java.classLoader, arrayOf(Project::class.java), handler) as Project
|
||||
}
|
||||
Reference in New Issue
Block a user