mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-233558 IJ-MR-184958 IJ-MR-184993 IJ-MR-179029 IJ-MR-184126 IJ-MR-181153 IJ-MR-146078 IJ-MR-175479 IJ-MR-186058 feat(platform): hide project toolwindow via layout profile
GitOrigin-RevId: f332445e972bb1ed26d8a3f7e4e5905379986059
This commit is contained in:
committed by
intellij-monorepo-bot
parent
59668c448d
commit
a7bc6397b3
+21
-10
@@ -10,8 +10,7 @@ import com.intellij.testFramework.junit5.RunInEdt
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.junit5.TestDisposable
|
||||
import com.intellij.testFramework.junit5.fixture.projectFixture
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
@@ -26,7 +25,12 @@ class ProjectFrameCapabilitiesServiceTest {
|
||||
|
||||
@Test
|
||||
fun uiPolicyUsesAggregatedCapabilitiesAndCapabilitiesAreCached() {
|
||||
val uiPolicyRef = AtomicReference(ProjectFrameUiPolicy(projectPaneToActivateId = "pane-1"))
|
||||
val uiPolicyRef = AtomicReference(
|
||||
ProjectFrameUiPolicy(
|
||||
projectPaneToActivateId = "pane-1",
|
||||
toolWindowLayoutProfileId = "layout-profile-1",
|
||||
)
|
||||
)
|
||||
val capabilitiesComputationCount = AtomicInteger()
|
||||
val capabilitiesRef = AtomicReference(setOf(ProjectFrameCapability.WELCOME_EXPERIENCE))
|
||||
ExtensionTestUtil.maskExtensions(
|
||||
@@ -56,16 +60,23 @@ class ProjectFrameCapabilitiesServiceTest {
|
||||
)
|
||||
|
||||
val service = ProjectFrameCapabilitiesService(service<CoreUiCoroutineScopeHolder>().coroutineScope)
|
||||
assertEquals("pane-1", service.getUiPolicy(project)?.projectPaneToActivateId)
|
||||
assertThat(service.getUiPolicy(project)?.projectPaneToActivateId).isEqualTo("pane-1")
|
||||
assertThat(service.getUiPolicy(project)?.toolWindowLayoutProfileId).isEqualTo("layout-profile-1")
|
||||
|
||||
uiPolicyRef.set(ProjectFrameUiPolicy(projectPaneToActivateId = "pane-2"))
|
||||
assertEquals("pane-2", service.getUiPolicy(project)?.projectPaneToActivateId)
|
||||
uiPolicyRef.set(
|
||||
ProjectFrameUiPolicy(
|
||||
projectPaneToActivateId = "pane-2",
|
||||
toolWindowLayoutProfileId = "layout-profile-2",
|
||||
)
|
||||
)
|
||||
assertThat(service.getUiPolicy(project)?.projectPaneToActivateId).isEqualTo("pane-2")
|
||||
assertThat(service.getUiPolicy(project)?.toolWindowLayoutProfileId).isEqualTo("layout-profile-2")
|
||||
|
||||
assertEquals(setOf(ProjectFrameCapability.WELCOME_EXPERIENCE), service.getAll(project))
|
||||
assertTrue(service.has(project, ProjectFrameCapability.WELCOME_EXPERIENCE))
|
||||
assertThat(service.getAll(project)).isEqualTo(setOf(ProjectFrameCapability.WELCOME_EXPERIENCE))
|
||||
assertThat(service.has(project, ProjectFrameCapability.WELCOME_EXPERIENCE)).isTrue()
|
||||
|
||||
capabilitiesRef.set(emptySet())
|
||||
assertEquals(setOf(ProjectFrameCapability.WELCOME_EXPERIENCE), service.getAll(project))
|
||||
assertEquals(1, capabilitiesComputationCount.get())
|
||||
assertThat(service.getAll(project)).isEqualTo(setOf(ProjectFrameCapability.WELCOME_EXPERIENCE))
|
||||
assertThat(capabilitiesComputationCount.get()).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// 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.toolWindow
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.impl.DesktopLayout
|
||||
import com.intellij.testFramework.ExtensionTestUtil
|
||||
import com.intellij.testFramework.junit5.RunInEdt
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.junit5.TestDisposable
|
||||
import com.intellij.testFramework.junit5.fixture.projectFixture
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@TestApplication
|
||||
@RunInEdt
|
||||
class ToolWindowLayoutProfileServiceTest {
|
||||
private val project by projectFixture()
|
||||
|
||||
@TestDisposable
|
||||
private lateinit var disposable: Disposable
|
||||
|
||||
@Test
|
||||
fun returnsLayoutFromFirstMatchingProvider() {
|
||||
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" }
|
||||
}
|
||||
},
|
||||
object : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
return null
|
||||
}
|
||||
},
|
||||
),
|
||||
disposable,
|
||||
)
|
||||
|
||||
val result = service<ToolWindowLayoutProfileService>().getLayout(project = project, profileId = "dedicated", isNewUi = true)
|
||||
|
||||
assertThat(result).isSameAs(firstLayout)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun returnsNullWhenNoProviderMatchesProfileId() {
|
||||
ExtensionTestUtil.maskExtensions(
|
||||
ToolWindowLayoutProfileProvider.EP_NAME,
|
||||
listOf(
|
||||
object : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
return null
|
||||
}
|
||||
},
|
||||
),
|
||||
disposable,
|
||||
)
|
||||
|
||||
val result = service<ToolWindowLayoutProfileService>().getLayout(project = project, profileId = "missing", isNewUi = true)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@ 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
|
||||
@@ -101,6 +102,7 @@ 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
|
||||
@@ -424,7 +426,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
|
||||
val updateHeadersRequests = MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
coroutineScope.launch {
|
||||
updateHeadersRequests
|
||||
.debounce(50)
|
||||
.debounce(50.milliseconds)
|
||||
.collectLatest {
|
||||
for (project in getOpenedProjects()) {
|
||||
val toolWindowManager = project.serviceAsync<ToolWindowManager>() as ToolWindowManagerImpl
|
||||
@@ -774,7 +776,14 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
|
||||
}
|
||||
|
||||
private fun loadDefault() {
|
||||
toolWindowSetInitializer.scheduleSetLayout(ToolWindowDefaultLayoutManager.getInstance().getLayoutCopy())
|
||||
val layout = getLayoutForProjectFrameProfile() ?: 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)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.toolWindow
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.impl.DesktopLayout
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
/**
|
||||
* Resolves a toolwindow layout for a specific project frame profile.
|
||||
*
|
||||
* The layout is used to seed a frame when no project-specific layout was persisted yet.
|
||||
*/
|
||||
@Internal
|
||||
interface ToolWindowLayoutProfileProvider {
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
val EP_NAME: ExtensionPointName<ToolWindowLayoutProfileProvider> = ExtensionPointName("com.intellij.toolWindowLayoutProfileProvider")
|
||||
}
|
||||
|
||||
fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout?
|
||||
}
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@Internal
|
||||
class ToolWindowLayoutProfileService {
|
||||
fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
if (project.isDisposed) {
|
||||
return null
|
||||
}
|
||||
|
||||
val providers = ToolWindowLayoutProfileProvider.EP_NAME.extensionList
|
||||
if (providers.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
var layout: DesktopLayout? = null
|
||||
var layoutProvider: 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
|
||||
}
|
||||
else {
|
||||
LOG.error(
|
||||
"Multiple tool window layouts are provided for profile '$profileId'. " +
|
||||
"Keeping ${layoutProvider?.javaClass?.name}, ignoring ${provider.javaClass.name}."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error("Tool window layout profile provider '${provider.javaClass.name}' failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
}
|
||||
|
||||
private val LOG = logger<ToolWindowLayoutProfileService>()
|
||||
@@ -216,6 +216,7 @@
|
||||
</extensionPoint>
|
||||
|
||||
<extensionPoint name="defaultToolWindowLayout" interface="com.intellij.toolWindow.DefaultToolWindowLayoutExtension" dynamic="true"/>
|
||||
<extensionPoint name="toolWindowLayoutProfileProvider" interface="com.intellij.toolWindow.ToolWindowLayoutProfileProvider" dynamic="true"/>
|
||||
<extensionPoint name="toolWindow.splitContentProvider" beanClass="com.intellij.toolWindow.ToolWindowSplitContentProviderBean" dynamic="true">
|
||||
<with attribute="implementationClass" implements="com.intellij.toolWindow.ToolWindowSplitContentProvider"/>
|
||||
</extensionPoint>
|
||||
|
||||
@@ -54,9 +54,18 @@ data class ProjectFrameUiPolicy(
|
||||
|
||||
/** Toolwindow ids to hide after startup activation. */
|
||||
val toolWindowIdsToHideOnStartup: Set<String> = emptySet(),
|
||||
|
||||
/**
|
||||
* Toolwindow layout profile id used to seed project frame layout on first open (when no
|
||||
* project-specific toolwindow layout has been persisted yet).
|
||||
*/
|
||||
val toolWindowLayoutProfileId: String? = null,
|
||||
) {
|
||||
fun isEmpty(): Boolean {
|
||||
return projectPaneToActivateId == null && startupToolWindowIdToActivate == null && toolWindowIdsToHideOnStartup.isEmpty()
|
||||
return projectPaneToActivateId == null &&
|
||||
startupToolWindowIdToActivate == null &&
|
||||
toolWindowIdsToHideOnStartup.isEmpty() &&
|
||||
toolWindowLayoutProfileId == null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
bundle="messages.AgentSessionsBundle"
|
||||
groupKey="advanced.settings.agent.workbench"/>
|
||||
<projectFrameCapabilitiesProvider implementation="com.intellij.agent.workbench.sessions.AgentWorkbenchProjectFrameCapabilitiesProvider"/>
|
||||
<toolWindowLayoutProfileProvider implementation="com.intellij.agent.workbench.sessions.AgentWorkbenchToolWindowLayoutProfileProvider"/>
|
||||
</extensions>
|
||||
|
||||
<actions resource-bundle="messages.AgentSessionsBundle">
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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
|
||||
|
||||
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"
|
||||
+5
-2
@@ -6,6 +6,7 @@ import com.intellij.openapi.wm.ToolWindowId
|
||||
import com.intellij.openapi.wm.ex.ProjectFrameCapabilitiesProvider
|
||||
import com.intellij.openapi.wm.ex.ProjectFrameCapability
|
||||
import com.intellij.openapi.wm.ex.ProjectFrameUiPolicy
|
||||
import java.util.EnumSet
|
||||
|
||||
internal class AgentWorkbenchProjectFrameCapabilitiesProvider : ProjectFrameCapabilitiesProvider {
|
||||
override fun getCapabilities(project: Project): Set<ProjectFrameCapability> {
|
||||
@@ -33,8 +34,10 @@ internal class AgentWorkbenchProjectFrameCapabilitiesProvider : ProjectFrameCapa
|
||||
}
|
||||
}
|
||||
|
||||
private val AGENT_WORKBENCH_FRAME_CAPABILITIES = setOf(ProjectFrameCapability.SUPPRESS_VCS_UI)
|
||||
private val AGENT_WORKBENCH_FRAME_CAPABILITIES = EnumSet.of(ProjectFrameCapability.SUPPRESS_VCS_UI)
|
||||
|
||||
private val AGENT_WORKBENCH_FRAME_UI_POLICY = ProjectFrameUiPolicy(
|
||||
startupToolWindowIdToActivate = "agent.workbench.sessions",
|
||||
startupToolWindowIdToActivate = AGENT_SESSIONS_TOOL_WINDOW_ID,
|
||||
toolWindowIdsToHideOnStartup = setOf(ToolWindowId.PROJECT_VIEW),
|
||||
toolWindowLayoutProfileId = AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
|
||||
|
||||
package com.intellij.agent.workbench.sessions
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindowAnchor
|
||||
import com.intellij.openapi.wm.ToolWindowId
|
||||
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.ToolWindowLayoutProfileProvider
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
|
||||
internal class AgentWorkbenchToolWindowLayoutProfileProvider : ToolWindowLayoutProfileProvider {
|
||||
override fun getLayout(project: Project, profileId: String, isNewUi: Boolean): DesktopLayout? {
|
||||
if (project.isDisposed) {
|
||||
return null
|
||||
}
|
||||
if (profileId != AGENT_WORKBENCH_DEDICATED_LAYOUT_PROFILE_ID) {
|
||||
return null
|
||||
}
|
||||
|
||||
val baseLayout = ToolWindowDefaultLayoutManager.getInstance().getLayoutCopy()
|
||||
val infos = baseLayout.getInfos()
|
||||
.asSequence()
|
||||
.filter { (id, _) -> id != ToolWindowId.PROJECT_VIEW }
|
||||
.associateTo(LinkedHashMap()) { (id, info) -> id to info.copy() }
|
||||
|
||||
val sessionsInfo = infos.get(AGENT_SESSIONS_TOOL_WINDOW_ID) ?: WindowInfoImpl()
|
||||
val paneId = WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
|
||||
val nextOrderOnLeft = infos.values.asSequence()
|
||||
.filter { it.toolWindowPaneId == paneId && it.anchor == ToolWindowAnchor.LEFT && it.order >= 0 }
|
||||
.maxOfOrNull { it.order + 1 } ?: 0
|
||||
|
||||
sessionsInfo.id = AGENT_SESSIONS_TOOL_WINDOW_ID
|
||||
sessionsInfo.toolWindowPaneId = paneId
|
||||
sessionsInfo.anchor = ToolWindowAnchor.LEFT
|
||||
sessionsInfo.order = nextOrderOnLeft
|
||||
sessionsInfo.isVisible = true
|
||||
sessionsInfo.isShowStripeButton = true
|
||||
sessionsInfo.weight = 0.25f
|
||||
infos.put(AGENT_SESSIONS_TOOL_WINDOW_ID, sessionsInfo)
|
||||
|
||||
return DesktopLayout(infos, baseLayout.unifiedWeights.copy())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user