From 7a1ba51253cb82ad296172c73fcda1d6c1cd161c Mon Sep 17 00:00:00 2001 From: Tatiana Elfimova Date: Tue, 24 Jun 2025 09:49:57 +0200 Subject: [PATCH] IJPL-186470 Search Everywhere for RD: make popup compact if empty query - Code review fixes and refactoring GitOrigin-RevId: 7e84d88bde5cf65c5c72d9eb811f3047f0180890 --- .../frontend/src/SeFrontendService.kt | 300 +++++++++++++++++- .../frontend/src/ui/SePopupContentPane.kt | 66 ++-- 2 files changed, 333 insertions(+), 33 deletions(-) diff --git a/platform/searchEverywhere/frontend/src/SeFrontendService.kt b/platform/searchEverywhere/frontend/src/SeFrontendService.kt index 29b977e4087f..4164628e53aa 100644 --- a/platform/searchEverywhere/frontend/src/SeFrontendService.kt +++ b/platform/searchEverywhere/frontend/src/SeFrontendService.kt @@ -1 +1,299 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.platform.searchEverywhere.frontend import com.intellij.ide.actions.SearchEverywhereManagerFactory import com.intellij.ide.actions.searcheverywhere.* import com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.EDT import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.WindowStateService import com.intellij.openapi.wm.WindowManager import com.intellij.platform.searchEverywhere.SeSessionEntity import com.intellij.platform.searchEverywhere.frontend.tabs.actions.SeActionsTab import com.intellij.platform.searchEverywhere.frontend.tabs.all.SeAllTab import com.intellij.platform.searchEverywhere.frontend.tabs.classes.SeClassesTab import com.intellij.platform.searchEverywhere.frontend.tabs.files.SeFilesTab import com.intellij.platform.searchEverywhere.frontend.tabs.symbols.SeSymbolsTab import com.intellij.platform.searchEverywhere.frontend.tabs.text.SeTextTab import com.intellij.platform.searchEverywhere.frontend.ui.SePopupContentPane import com.intellij.platform.searchEverywhere.frontend.ui.SePopupContentPane.PopupViewMode import com.intellij.platform.searchEverywhere.frontend.vm.SePopupVm import com.intellij.platform.searchEverywhere.providers.SeLog import com.intellij.platform.searchEverywhere.providers.SeLog.LIFE_CYCLE import com.intellij.platform.searchEverywhere.providers.SeProvidersHolder import com.intellij.platform.searchEverywhere.providers.computeCatchingOrNull import com.intellij.platform.searchEverywhere.utils.SuspendLazyProperty import com.intellij.platform.searchEverywhere.utils.initAsync import com.intellij.platform.util.coroutines.childScope import com.intellij.platform.util.coroutines.sync.OverflowSemaphore import com.intellij.ui.ScreenUtil import com.intellij.ui.awt.RelativePoint import com.intellij.util.ui.StartupUiUtil import com.intellij.util.ui.UIUtil import fleet.kernel.DurableRef import fleet.kernel.change import fleet.kernel.shared import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import org.jetbrains.annotations.ApiStatus import java.awt.KeyboardFocusManager import java.awt.Point import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent import java.util.concurrent.atomic.AtomicBoolean import javax.swing.SwingUtilities @ApiStatus.Internal @Service(Service.Level.PROJECT, Service.Level.APP) class SeFrontendService(val project: Project?, private val coroutineScope: CoroutineScope) : SearchEverywhereManager { @Suppress("unused") constructor(coroutineScope: CoroutineScope) : this(null, coroutineScope) private val popupSemaphore = OverflowSemaphore(1, overflow = BufferOverflow.DROP_LATEST) @Volatile private var popupInstance: SePopupInstance? = null @Volatile var localProvidersHolder: SeProvidersHolder? = null private set private val historyList = SearchHistoryList(true) val removeSessionRef: AtomicBoolean = AtomicBoolean(true) override fun show(tabId: String, searchText: String?, initEvent: AnActionEvent) { coroutineScope.launch { val popupScope = coroutineScope.childScope("SearchEverywhereFrontendService popup scope") val sessionRef = SeSessionEntity.createRef() try { popupSemaphore.withPermit { localProvidersHolder = SeProvidersHolder.initialize(initEvent, project, sessionRef, "Frontend") val completable = doShowPopup(tabId, searchText, initEvent, popupScope, sessionRef) completable.await() } } finally { popupInstance = null localProvidersHolder?.let { Disposer.dispose(it) } localProvidersHolder = null withContext(NonCancellable) { popupScope.cancel() if (removeSessionRef.get()) { change { shared { sessionRef.derefOrNull()?.delete() } } } } } } } private suspend fun doShowPopup( tabId: String, searchText: String?, initEvent: AnActionEvent, popupScope: CoroutineScope, sessionRef: DurableRef, ): CompletableDeferred { val startTime = System.currentTimeMillis() val tabInitializationTimoutMillis: Long = 50 val tabFactories = SeTabFactory.EP_NAME.extensionList val orderedTabFactoryIds = tabFactories.map { it.id } val tabsOrDeferredTabs = tabFactories.map { it.id to initAsync(popupScope) { computeCatchingOrNull({ e -> "Error while getting tab from ${it.id} tab factory: ${e.message}" }) { it.getTab(popupScope, project, sessionRef, initEvent) } } }.map { (loadingTabId, tabLoadingProperty) -> popupScope.async { withTimeoutOrNull(tabInitializationTimoutMillis) { tabLoadingProperty.getValue() } ?: run { if ((tabId + MAIN_TABS).contains(loadingTabId)) { SeLog.warn("Tab $tabId initialization took too long (> ${tabInitializationTimoutMillis}ms), waiting it's initialization anyway") // If we have to open this tab right after the popup is there, we wait until it gets initialized tabLoadingProperty.getValue() } else { SeLog.log(LIFE_CYCLE) { "Tab $tabId initialization took too long (> ${tabInitializationTimoutMillis}ms), will be initialized later" } tabLoadingProperty } } } }.awaitAll() val tabs = tabsOrDeferredTabs.filterIsInstance().sortedWith { tab1, tab2 -> val order1 = orderedTabFactoryIds.indexOf(tab1.id).let { if (it == -1) orderedTabFactoryIds.size + 1 else it } val order2 = orderedTabFactoryIds.indexOf(tab2.id).let { if (it == -1) orderedTabFactoryIds.size + 1 else it } order1 - order2 } val deferredTabs = tabsOrDeferredTabs.filterIsInstance>() var popup: JBPopup? = null val popupVm = SePopupVm(popupScope, project, sessionRef, tabs, deferredTabs, searchText, tabId, historyList) { popup?.cancel() popup = null } popupVm.showTab(tabId) val completable = CompletableDeferred() withContext(Dispatchers.EDT) { val contentPane = SePopupContentPane(project, popupVm, { size -> popup?.let { it.size = size } }) { popupScope.launch(NonCancellable) { removeSessionRef.set(false) try { popupVm.openInFindWindow(sessionRef, initEvent) } finally { change { shared { sessionRef.derefOrNull()?.delete() } } } } popupScope.cancel() } popup = createPopup(contentPane, popupVm, project) { completable.complete(Unit) } popup?.let { popup -> calcPopupPositionAndShow(popup, contentPane) } popupInstance = SePopupInstance(popupVm, contentPane) val endTime = System.currentTimeMillis() SeLog.log { "Search Everywhere popup opened in ${endTime - startTime} ms" } } return completable } private fun createPopup(panel: SePopupContentPane, popupVm: SePopupVm, project: Project?, onCancel: () -> Unit): JBPopup { val popup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel.preferableFocusedComponent) .setProject(project) .setModalContext(false) .setNormalWindowLevel(StartupUiUtil.isWaylandToolkit()) .setCancelOnClickOutside(true) .setRequestFocus(true) .setCancelKeyEnabled(false) .setResizable(true) .setMovable(true) .setDimensionServiceKey(project, POPUP_LOCATION_SETTINGS_KEY, true) .setLocateWithinScreenBounds(false) .setCancelCallback { popupVm.saveSearchText() onCancel() SearchEverywhereUsageTriggerCollector.DIALOG_CLOSED.log(project, true) true } .createPopup() popup.size = panel.preferredSize popup.content.addComponentListener(object : ComponentAdapter() { override fun componentResized(e: ComponentEvent?) { if (project != null && panel.popupViewMode == PopupViewMode.EXPANDED) { panel.popupExtendedSize = popup.size } } }) Disposer.register(popup) { project?.let { getStateService().putSize(POPUP_LOCATION_SETTINGS_KEY, panel.popupExtendedSize) } Disposer.dispose(panel) } return popup } private fun calcPopupPositionAndShow(popup: JBPopup, panel: SePopupContentPane) { val savedLocation: Point? = getStateService().getLocation(POPUP_LOCATION_SETTINGS_KEY) // for first show and short mode popup should be shifted to the top screen half if (savedLocation == null && panel.popupViewMode == PopupViewMode.COMPACT) { val window = if (project != null) WindowManager.getInstance().suggestParentWindow(project) else KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow val parent = UIUtil.findUltimateParent(window) if (parent != null) { val popupSize = popup.size val screenPoint = Point((parent.size.width - popupSize.width) / 2, parent.getHeight() / 4 - popupSize.height / 2) SwingUtilities.convertPointToScreen(screenPoint, parent) val screenRectangle = ScreenUtil.getScreenRectangle(screenPoint) val insets = panel.getInsets() val bottomEdge: Int = screenPoint.y + panel.getExpandedSize().height + insets.bottom + insets.top val shift = bottomEdge - screenRectangle.maxY.toInt() if (shift > 0) { screenPoint.y = Integer.max(screenPoint.y - shift, screenRectangle.y) } val showPoint = RelativePoint(screenPoint) popup.show(showPoint) return } } if (project != null) { popup.showCenteredInCurrentWindow(project) } else { popup.showInFocusCenter() } } private fun getStateService(): WindowStateService { return if (project != null) WindowStateService.getInstance(project) else WindowStateService.getInstance() } override fun isShown(): Boolean = popupInstance != null @Deprecated("Deprecated in the interface") override fun getCurrentlyShownUI(): SearchEverywhereUI { throw UnsupportedOperationException("The method is deprecated. Please use getCurrentlyShownPopupInstance() instead.") } override fun getCurrentlyShownPopupInstance(): SearchEverywherePopupInstance? = popupInstance override fun getSelectedTabID(): String = popupInstance?.getSelectedTabID() ?: "" override fun setSelectedTabID(tabID: String) { popupInstance?.setSelectedTabID(tabID) } override fun toggleEverywhereFilter() { popupInstance?.toggleEverywhereFilter() } override fun isEverywhere(): Boolean = popupInstance?.isEverywhere() ?: false @ApiStatus.Internal override fun isSplit(): Boolean = true companion object { @JvmStatic fun getInstance(project: Project?): SeFrontendService = project?.getService(SeFrontendService::class.java) ?: service() @JvmStatic val isEnabled: Boolean get() = SearchEverywhereFeature.isSplit const val POPUP_LOCATION_SETTINGS_KEY: String = "search.everywhere.popup" private val MAIN_TABS = setOf(SeAllTab.ID, SeFilesTab.ID, SeClassesTab.ID, SeSymbolsTab.ID, SeActionsTab.ID, SeTextTab.ID) } } @ApiStatus.Internal class RemDevFriendlySearchEverywhereManager : SearchEverywhereManagerFactory { override fun isAvailable(): Boolean = SeFrontendService.isEnabled override fun getManager(project: Project?): SearchEverywhereManager = SeFrontendService.getInstance(project) } \ No newline at end of file +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.platform.searchEverywhere.frontend + +import com.intellij.ide.actions.SearchEverywhereManagerFactory +import com.intellij.ide.actions.searcheverywhere.* +import com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.EDT +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.popup.JBPopup +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.WindowStateService +import com.intellij.openapi.wm.WindowManager +import com.intellij.platform.searchEverywhere.SeSessionEntity +import com.intellij.platform.searchEverywhere.frontend.tabs.actions.SeActionsTab +import com.intellij.platform.searchEverywhere.frontend.tabs.all.SeAllTab +import com.intellij.platform.searchEverywhere.frontend.tabs.classes.SeClassesTab +import com.intellij.platform.searchEverywhere.frontend.tabs.files.SeFilesTab +import com.intellij.platform.searchEverywhere.frontend.tabs.symbols.SeSymbolsTab +import com.intellij.platform.searchEverywhere.frontend.tabs.text.SeTextTab +import com.intellij.platform.searchEverywhere.frontend.ui.SePopupContentPane +import com.intellij.platform.searchEverywhere.frontend.vm.SePopupVm +import com.intellij.platform.searchEverywhere.providers.SeLog +import com.intellij.platform.searchEverywhere.providers.SeLog.LIFE_CYCLE +import com.intellij.platform.searchEverywhere.providers.SeProvidersHolder +import com.intellij.platform.searchEverywhere.providers.computeCatchingOrNull +import com.intellij.platform.searchEverywhere.utils.SuspendLazyProperty +import com.intellij.platform.searchEverywhere.utils.initAsync +import com.intellij.platform.util.coroutines.childScope +import com.intellij.platform.util.coroutines.sync.OverflowSemaphore +import com.intellij.ui.ScreenUtil +import com.intellij.ui.awt.RelativePoint +import com.intellij.util.ui.StartupUiUtil +import com.intellij.util.ui.UIUtil +import fleet.kernel.DurableRef +import fleet.kernel.change +import fleet.kernel.shared +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import org.jetbrains.annotations.ApiStatus +import java.awt.KeyboardFocusManager +import java.awt.Point +import java.util.concurrent.atomic.AtomicBoolean +import javax.swing.SwingUtilities + +@ApiStatus.Internal +@Service(Service.Level.PROJECT, Service.Level.APP) +class SeFrontendService(val project: Project?, private val coroutineScope: CoroutineScope) : SearchEverywhereManager { + @Suppress("unused") + constructor(coroutineScope: CoroutineScope) : this(null, coroutineScope) + + private val popupSemaphore = OverflowSemaphore(1, overflow = BufferOverflow.DROP_LATEST) + + @Volatile + private var popupInstance: SePopupInstance? = null + + @Volatile + var localProvidersHolder: SeProvidersHolder? = null + private set + + private val historyList = SearchHistoryList(true) + + val removeSessionRef: AtomicBoolean = AtomicBoolean(true) + + override fun show(tabId: String, searchText: String?, initEvent: AnActionEvent) { + coroutineScope.launch { + val popupScope = coroutineScope.childScope("SearchEverywhereFrontendService popup scope") + val sessionRef = SeSessionEntity.createRef() + + try { + popupSemaphore.withPermit { + localProvidersHolder = SeProvidersHolder.initialize(initEvent, project, sessionRef, "Frontend") + val completable = doShowPopup(tabId, searchText, initEvent, popupScope, sessionRef) + completable.await() + } + } + finally { + popupInstance = null + localProvidersHolder?.let { Disposer.dispose(it) } + localProvidersHolder = null + + withContext(NonCancellable) { + popupScope.cancel() + if (removeSessionRef.get()) { + change { + shared { + sessionRef.derefOrNull()?.delete() + } + } + } + } + } + } + } + + private suspend fun doShowPopup( + tabId: String, + searchText: String?, + initEvent: AnActionEvent, + popupScope: CoroutineScope, + sessionRef: DurableRef, + ): CompletableDeferred { + val startTime = System.currentTimeMillis() + val tabInitializationTimoutMillis: Long = 50 + + val tabFactories = SeTabFactory.EP_NAME.extensionList + val orderedTabFactoryIds = tabFactories.map { it.id } + + val tabsOrDeferredTabs = tabFactories.map { + it.id to initAsync(popupScope) { + computeCatchingOrNull({ e -> "Error while getting tab from ${it.id} tab factory: ${e.message}" }) { + it.getTab(popupScope, project, sessionRef, initEvent) + } + } + }.map { (loadingTabId, tabLoadingProperty) -> + popupScope.async { + withTimeoutOrNull(tabInitializationTimoutMillis) { + tabLoadingProperty.getValue() + } ?: run { + if ((tabId + MAIN_TABS).contains(loadingTabId)) { + SeLog.warn("Tab $tabId initialization took too long (> ${tabInitializationTimoutMillis}ms), waiting it's initialization anyway") + // If we have to open this tab right after the popup is there, we wait until it gets initialized + tabLoadingProperty.getValue() + } + else { + SeLog.log(LIFE_CYCLE) { "Tab $tabId initialization took too long (> ${tabInitializationTimoutMillis}ms), will be initialized later" } + tabLoadingProperty + } + } + } + }.awaitAll() + + val tabs = tabsOrDeferredTabs.filterIsInstance().sortedWith { tab1, tab2 -> + val order1 = orderedTabFactoryIds.indexOf(tab1.id).let { if (it == -1) orderedTabFactoryIds.size + 1 else it } + val order2 = orderedTabFactoryIds.indexOf(tab2.id).let { if (it == -1) orderedTabFactoryIds.size + 1 else it } + + order1 - order2 + } + val deferredTabs = tabsOrDeferredTabs.filterIsInstance>() + + var popup: JBPopup? = null + val popupVm = SePopupVm(popupScope, project, sessionRef, tabs, deferredTabs, searchText, tabId, historyList) { + popup?.cancel() + popup = null + } + popupVm.showTab(tabId) + + val completable = CompletableDeferred() + withContext(Dispatchers.EDT) { + val contentPane = SePopupContentPane(project, popupVm, { size -> popup?.let { it.size = size } }, getStateService().getSize(POPUP_LOCATION_SETTINGS_KEY)) { + popupScope.launch(NonCancellable) { + removeSessionRef.set(false) + try { + popupVm.openInFindWindow(sessionRef, initEvent) + } finally { + change { + shared { + sessionRef.derefOrNull()?.delete() + } + } + } + } + popupScope.cancel() + } + + popup = createPopup(contentPane, popupVm, project) { + completable.complete(Unit) + } + + popup?.let { popup -> + calcPopupPositionAndShow(popup, contentPane) + } + + popupInstance = SePopupInstance(popupVm, contentPane) + + val endTime = System.currentTimeMillis() + SeLog.log { "Search Everywhere popup opened in ${endTime - startTime} ms" } + } + return completable + } + + private fun createPopup(panel: SePopupContentPane, popupVm: SePopupVm, project: Project?, onCancel: () -> Unit): JBPopup { + val popup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel.preferableFocusedComponent) + .setProject(project) + .setModalContext(false) + .setNormalWindowLevel(StartupUiUtil.isWaylandToolkit()) + .setCancelOnClickOutside(true) + .setRequestFocus(true) + .setCancelKeyEnabled(false) + .setResizable(true) + .setMovable(true) + .setDimensionServiceKey(project, POPUP_LOCATION_SETTINGS_KEY, true) + .setLocateWithinScreenBounds(false) + .setCancelCallback { + popupVm.saveSearchText() + onCancel() + SearchEverywhereUsageTriggerCollector.DIALOG_CLOSED.log(project, true) + true + } + .createPopup() + + popup.size = panel.preferredSize + + Disposer.register(popup) { + getStateService().putSize(POPUP_LOCATION_SETTINGS_KEY, panel.popupExtendedSize) + Disposer.dispose(panel) + } + + return popup + } + + private fun calcPopupPositionAndShow(popup: JBPopup, panel: SePopupContentPane) { + val savedLocation: Point? = getStateService().getLocation(POPUP_LOCATION_SETTINGS_KEY) + + // for first show and short mode popup should be shifted to the top screen half + if (savedLocation == null && panel.isCompactViewMode) { + val window = if (project != null) + WindowManager.getInstance().suggestParentWindow(project) + else + KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow + val parent = UIUtil.findUltimateParent(window) + + if (parent != null) { + val popupSize = popup.size + + val screenPoint = Point((parent.size.width - popupSize.width) / 2, parent.getHeight() / 4 - popupSize.height / 2) + SwingUtilities.convertPointToScreen(screenPoint, parent) + + val screenRectangle = ScreenUtil.getScreenRectangle(screenPoint) + val insets = panel.getInsets() + val bottomEdge: Int = screenPoint.y + panel.getExpandedSize().height + insets.bottom + insets.top + val shift = bottomEdge - screenRectangle.maxY.toInt() + if (shift > 0) { + screenPoint.y = Integer.max(screenPoint.y - shift, screenRectangle.y) + } + + val showPoint = RelativePoint(screenPoint) + popup.show(showPoint) + return + } + } + + if (project != null) { + popup.showCenteredInCurrentWindow(project) + } + else { + popup.showInFocusCenter() + } + } + + private fun getStateService(): WindowStateService { + return if (project != null) WindowStateService.getInstance(project) else WindowStateService.getInstance() + } + + override fun isShown(): Boolean = popupInstance != null + + @Deprecated("Deprecated in the interface") + override fun getCurrentlyShownUI(): SearchEverywhereUI { + throw UnsupportedOperationException("The method is deprecated. Please use getCurrentlyShownPopupInstance() instead.") + } + + override fun getCurrentlyShownPopupInstance(): SearchEverywherePopupInstance? = popupInstance + + override fun getSelectedTabID(): String = popupInstance?.getSelectedTabID() ?: "" + + override fun setSelectedTabID(tabID: String) { + popupInstance?.setSelectedTabID(tabID) + } + + override fun toggleEverywhereFilter() { + popupInstance?.toggleEverywhereFilter() + } + + override fun isEverywhere(): Boolean = popupInstance?.isEverywhere() ?: false + + @ApiStatus.Internal + override fun isSplit(): Boolean = true + + companion object { + @JvmStatic + fun getInstance(project: Project?): SeFrontendService = project?.getService(SeFrontendService::class.java) + ?: service() + + @JvmStatic + val isEnabled: Boolean get() = SearchEverywhereFeature.isSplit + + internal const val POPUP_LOCATION_SETTINGS_KEY: String = "search.everywhere.popup" + private val MAIN_TABS = setOf(SeAllTab.ID, SeFilesTab.ID, SeClassesTab.ID, SeSymbolsTab.ID, SeActionsTab.ID, SeTextTab.ID) + } +} + +@ApiStatus.Internal +class RemDevFriendlySearchEverywhereManager : SearchEverywhereManagerFactory { + override fun isAvailable(): Boolean = SeFrontendService.isEnabled + override fun getManager(project: Project?): SearchEverywhereManager = SeFrontendService.getInstance(project) +} \ No newline at end of file diff --git a/platform/searchEverywhere/frontend/src/ui/SePopupContentPane.kt b/platform/searchEverywhere/frontend/src/ui/SePopupContentPane.kt index 9b87e0109b7f..441a503a6c37 100644 --- a/platform/searchEverywhere/frontend/src/ui/SePopupContentPane.kt +++ b/platform/searchEverywhere/frontend/src/ui/SePopupContentPane.kt @@ -19,13 +19,11 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.ListItemDescriptorAdapter import com.intellij.openapi.util.NlsContexts -import com.intellij.openapi.util.WindowStateService import com.intellij.openapi.util.registry.Registry import com.intellij.platform.searchEverywhere.SeActionItemPresentation import com.intellij.platform.searchEverywhere.SeTargetItemPresentation import com.intellij.platform.searchEverywhere.SeTextSearchItemPresentation import com.intellij.platform.searchEverywhere.frontend.AutoToggleAction -import com.intellij.platform.searchEverywhere.frontend.SeFrontendService.Companion.POPUP_LOCATION_SETTINGS_KEY import com.intellij.platform.searchEverywhere.frontend.tabs.actions.SeActionItemPresentationRenderer import com.intellij.platform.searchEverywhere.frontend.tabs.files.SeTargetItemPresentationRenderer import com.intellij.platform.searchEverywhere.frontend.tabs.text.SeTextSearchItemPresentationRenderer @@ -66,7 +64,10 @@ import kotlin.math.roundToInt @OptIn(ExperimentalAtomicApi::class, ExperimentalCoroutinesApi::class) @Internal -class SePopupContentPane(private val project: Project?, private val vm: SePopupVm, private val resizePopupHandler: (Dimension) -> Unit, onShowFindToolWindow: () -> Unit) : JPanel(), Disposable, UiDataProvider { +class SePopupContentPane(private val project: Project?, private val vm: SePopupVm, + private val resizePopupHandler: (Dimension) -> Unit, + initPopupExtendedSize: Dimension?, + onShowFindToolWindow: () -> Unit) : JPanel(), Disposable, UiDataProvider { val preferableFocusedComponent: JComponent get() = textField val searchFieldDocument: Document get() = textField.document @@ -96,10 +97,9 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV private val isSearchCompleted: AtomicBoolean = AtomicBoolean(false) - var popupViewMode: PopupViewMode = PopupViewMode.COMPACT + var isCompactViewMode: Boolean = true private set - var popupExtendedSize: Dimension? = (project?.let { WindowStateService.getInstance(it) } - ?: WindowStateService.getInstance()).getSize(POPUP_LOCATION_SETTINGS_KEY) + var popupExtendedSize: Dimension? = initPopupExtendedSize init { layout = GridLayout() @@ -138,7 +138,7 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV .row().cell(extendedInfoContainer, horizontalAlign = HorizontalAlign.FILL, resizableColumn = true) // hide resultsScrollPane and extendedInfoContainer - switchViewMode() + updateViewMode() textField.launchOnShow("Search Everywhere text field text binding") { withContext(Dispatchers.EDT) { @@ -194,7 +194,7 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV updateEmptyStatus() } - switchViewMode() + updateViewMode() } }.collect { event -> withContext(Dispatchers.EDT) { @@ -212,7 +212,7 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV resultList.selectedIndex = 0 } - switchViewMode() + updateViewMode() } } } @@ -265,6 +265,13 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV } WindowMoveListener(this).installTo(headerPane) + addComponentListener(object : ComponentAdapter() { + override fun componentResized(e: ComponentEvent?) { + if (project != null && !isCompactViewMode) { + popupExtendedSize = size + } + } + }) DumbAwareAction.create { vm.getHistoryItem(true)?.let { textField.text = it; textField.selectAll() } } .registerCustomShortcutSet(SearchTextField.SHOW_HISTORY_SHORTCUT, this) @@ -628,41 +635,41 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV } } - private fun switchViewMode() { + private fun updateViewMode() { if (textField.text.isEmpty() && resultList.isEmpty) { - switchViewMode(true) + updateViewMode(true) } else { - switchViewMode(false) + updateViewMode(false) } } - private fun switchViewMode(compact: Boolean) { - if (compact == (popupViewMode == PopupViewMode.COMPACT)) return - popupViewMode = if (compact) PopupViewMode.COMPACT else PopupViewMode.EXPANDED - - resizePopupHandler(calcPreferredSize(popupViewMode)) - + private fun updateViewMode(compact: Boolean) { extendedInfoContainer.isVisible = !compact && isExtendedInfoEnabled() resultsScrollPane.isVisible = !compact + + if (compact == isCompactViewMode) return + isCompactViewMode = compact + + resizePopupHandler(calcPreferredSize(isCompactViewMode)) } fun getExpandedSize(): Dimension { - return calcPreferredSize(PopupViewMode.EXPANDED) + return calcPreferredSize(false) } override fun getPreferredSize(): Dimension { - return calcPreferredSize(popupViewMode) + return calcPreferredSize(isCompactViewMode) } - private fun calcPreferredSize(popupViewMode: PopupViewMode): Dimension { - val preferredHeight = when (popupViewMode) { - PopupViewMode.COMPACT -> - headerPane.preferredSize.height + textField.preferredSize.height - PopupViewMode.EXPANDED -> - popupExtendedSize?.height ?: JBUI.CurrentTheme.BigPopup.maxListHeight() + private fun calcPreferredSize(compact: Boolean): Dimension { + val preferredHeight = if (compact) { + headerPane.preferredSize.height + textField.preferredSize.height } - return Dimension(popupExtendedSize?.width ?: 670, preferredHeight) + else { + popupExtendedSize?.height ?: JBUI.CurrentTheme.BigPopup.maxListHeight() + } + return Dimension(popupExtendedSize?.width ?: resultsScrollPane.preferredSize.width, preferredHeight) } private fun logTabSwitchedEvent(e: AnActionEvent) { @@ -676,11 +683,6 @@ class SePopupContentPane(private val project: Project?, private val vm: SePopupV sink[PlatformDataKeys.PREDEFINED_TEXT] = textField.text } - enum class PopupViewMode { - COMPACT, - EXPANDED - } - /** * Custom move up action that moves to the end if the index is 0 and the search is completed */