mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[platform UI] IJPL-209442 Search Everywhere: show "No exact matches found. Here are similar results" hint in the search field when only semantic search results are listed
GitOrigin-RevId: 6eccd43bb3fb88a9a618df592974917a4926aa36
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d1782aa310
commit
30a4143f47
@@ -24,6 +24,7 @@ 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.registry.Registry
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.platform.searchEverywhere.*
|
||||
import com.intellij.platform.searchEverywhere.data.SeDataKeys
|
||||
import com.intellij.platform.searchEverywhere.frontend.AutoToggleAction
|
||||
@@ -122,6 +123,8 @@ class SePopupContentPane(
|
||||
private set
|
||||
var popupExtendedSize: Dimension? = initPopupExtendedSize
|
||||
|
||||
private val semanticWarning = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
layout = GridLayout()
|
||||
|
||||
@@ -265,6 +268,7 @@ class SePopupContentPane(
|
||||
updateEmptyStatus()
|
||||
}
|
||||
|
||||
semanticWarning.value = resultListModel.isValidAndHasOnlySemantic
|
||||
updateViewMode()
|
||||
autoSelectIndex(searchContext.searchPattern, true)
|
||||
}
|
||||
@@ -345,9 +349,12 @@ class SePopupContentPane(
|
||||
}
|
||||
|
||||
launch {
|
||||
vm.searchFieldHint.collect { hint ->
|
||||
combine(vm.searchFieldHint, semanticWarning) { searchFieldHint, semanticWarning ->
|
||||
searchFieldHint to semanticWarning
|
||||
}.collect { (searchFieldHint, semanticWarning) ->
|
||||
withContext(Dispatchers.EDT) {
|
||||
hint.let { (text, tooltip, isWarning) ->
|
||||
if (searchFieldHint != null) {
|
||||
val (text, tooltip, isWarning) = searchFieldHint
|
||||
if (isWarning) {
|
||||
hintHelper.setLoadingText(text, tooltip)
|
||||
}
|
||||
@@ -355,6 +362,15 @@ class SePopupContentPane(
|
||||
hintHelper.setHint(text)
|
||||
}
|
||||
}
|
||||
else if (semanticWarning) {
|
||||
val noExactMatchesText = SemanticBundleProvider.getSemanticBundle()?.getMessage("search.everywhere.no.exact.matches")?.let {
|
||||
StringUtil.trimTrailing(it, ':')
|
||||
}
|
||||
hintHelper.setHint(noExactMatchesText)
|
||||
}
|
||||
else {
|
||||
hintHelper.setHint(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@ package com.intellij.platform.searchEverywhere.frontend.ui
|
||||
import com.intellij.ide.rpc.ThrottledAccumulatedItems
|
||||
import com.intellij.ide.rpc.ThrottledItems
|
||||
import com.intellij.ide.rpc.ThrottledOneItem
|
||||
import com.intellij.platform.searchEverywhere.SeItemDataKeys
|
||||
import com.intellij.platform.searchEverywhere.SeResultAddedEvent
|
||||
import com.intellij.platform.searchEverywhere.SeResultEvent
|
||||
import com.intellij.platform.searchEverywhere.SeResultReplacedEvent
|
||||
import com.intellij.platform.searchEverywhere.frontend.SeSearchStatePublisher
|
||||
import com.intellij.platform.searchEverywhere.frontend.vm.SeSearchContext
|
||||
import com.intellij.platform.searchEverywhere.providers.SeLog
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.swing.ListSelectionModel
|
||||
import kotlin.text.toBoolean
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Internal
|
||||
class SeResultListModel(private val searchStatePublisher: SeSearchStatePublisher,
|
||||
private val selectionModelProvider: () -> ListSelectionModel): DefaultListModel<SeResultListRow>() {
|
||||
val freezer: Freezer = Freezer { size }
|
||||
@@ -26,16 +30,21 @@ class SeResultListModel(private val searchStatePublisher: SeSearchStatePublisher
|
||||
|
||||
val pendingReplacementElementUuids: MutableSet<String> = mutableSetOf()
|
||||
|
||||
var isValidAndHasOnlySemantic: Boolean = false
|
||||
private set
|
||||
|
||||
fun reset() {
|
||||
SeLog.log(SeLog.THROTTLING) { "Will reset result list model" }
|
||||
freezer.reset()
|
||||
_isValidState.value = true
|
||||
pendingReplacementElementUuids.clear()
|
||||
removeAllElements()
|
||||
isValidAndHasOnlySemantic = false
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
_isValidState.value = false
|
||||
isValidAndHasOnlySemantic = false
|
||||
}
|
||||
|
||||
fun removeLoadingItem() {
|
||||
@@ -47,6 +56,8 @@ class SeResultListModel(private val searchStatePublisher: SeSearchStatePublisher
|
||||
fun addFromThrottledEvent(searchContext: SeSearchContext, throttledEvent: ThrottledItems<SeResultEvent>) {
|
||||
if (!isValid) reset()
|
||||
|
||||
val wasEmpty = isEmpty
|
||||
|
||||
val resultListAdapter = SeResultListModelAdapter(this, selectionModelProvider())
|
||||
when (throttledEvent) {
|
||||
is ThrottledAccumulatedItems<SeResultEvent> -> {
|
||||
@@ -83,6 +94,8 @@ class SeResultListModel(private val searchStatePublisher: SeSearchStatePublisher
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
isValidAndHasOnlySemantic = (isValidAndHasOnlySemantic || (wasEmpty && !isEmpty)) && throttledEvent.allItemsAreSemantic()
|
||||
}
|
||||
|
||||
class Freezer(private val listSize: () -> Int) {
|
||||
@@ -109,4 +122,23 @@ class SeResultListModel(private val searchStatePublisher: SeSearchStatePublisher
|
||||
frozenCountToApply = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : SeResultEvent> ThrottledItems<T>.allItemsAreSemantic(): Boolean =
|
||||
when (this) {
|
||||
is ThrottledAccumulatedItems<T> -> items.all { it.isSemantic() }
|
||||
is ThrottledOneItem<T> -> item.isSemantic()
|
||||
}
|
||||
|
||||
private fun SeResultEvent.isSemantic(): Boolean {
|
||||
val itemData = when (this) {
|
||||
is SeResultAddedEvent -> {
|
||||
itemData
|
||||
}
|
||||
is SeResultReplacedEvent -> {
|
||||
newItemData
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return itemData?.additionalInfo?.get(SeItemDataKeys.IS_SEMANTIC).toBoolean()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2000-2025 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.ui
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
object SemanticBundleProvider {
|
||||
fun getSemanticBundle(): DynamicBundle? {
|
||||
val pluginId = PluginId.getId("com.intellij.ml.llm")
|
||||
if (!PluginManagerCore.isPluginInstalled(pluginId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val descriptor = PluginManagerCore.getPlugin(pluginId) ?: return null
|
||||
val classLoader = descriptor.pluginClassLoader
|
||||
|
||||
return try {
|
||||
val clazz = classLoader?.loadClass(
|
||||
"com.intellij.ml.llm.searchEverywhere.embeddings.SemanticSearchBundle"
|
||||
)
|
||||
val instanceField = clazz?.getDeclaredField("INSTANCE")
|
||||
instanceField?.get(null) as DynamicBundle
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,8 +66,8 @@ class SePopupVm(
|
||||
|
||||
data class SearchFieldHint(val text: String?, val tooltip: String?, val isWarning: Boolean)
|
||||
|
||||
private val _searchFieldHint = MutableStateFlow(SearchFieldHint("", null, false))
|
||||
val searchFieldHint: StateFlow<SearchFieldHint> = _searchFieldHint
|
||||
private val _searchFieldHint = MutableStateFlow<SearchFieldHint?>(null)
|
||||
val searchFieldHint: StateFlow<SearchFieldHint?> = _searchFieldHint
|
||||
|
||||
private var historyIterator: HistoryIterator = historyList.getIterator(currentTab.tabId)
|
||||
get() {
|
||||
@@ -151,11 +151,10 @@ class SePopupVm(
|
||||
SearchFieldHint(IdeBundle.message("incomplete.mode.results.might.be.incomplete"), null, true)
|
||||
}
|
||||
else {
|
||||
val text = if (currentTab.isCommandsSupported()) {
|
||||
IdeBundle.message("searcheverywhere.textfield.hint", getTopHitAccelerator())
|
||||
if (currentTab.isCommandsSupported()) {
|
||||
SearchFieldHint(IdeBundle.message("searcheverywhere.textfield.hint", getTopHitAccelerator()), null, false)
|
||||
}
|
||||
else null
|
||||
SearchFieldHint(text, null, false)
|
||||
}
|
||||
}.distinctUntilChanged().collect {
|
||||
_searchFieldHint.value = it
|
||||
|
||||
Reference in New Issue
Block a user