diff --git a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt index e00507bedc..c2838ec2ea 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt @@ -101,10 +101,12 @@ import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener import com.maddyhome.idea.vim.ui.ShowCmdWidgetUpdater +import com.maddyhome.idea.vim.ui.widgets.search.searchWidgetOptionListener import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener +import com.maddyhome.idea.vim.ui.widgets.search.SearchWidgetListener import org.jetbrains.annotations.TestOnly import java.awt.event.MouseAdapter import java.awt.event.MouseEvent @@ -169,6 +171,10 @@ internal object VimListenerManager { injector.listenersNotifier.myEditorListeners.add(modeWidgetListener) injector.listenersNotifier.vimPluginListeners.add(modeWidgetListener) + val searchWidgetListener = SearchWidgetListener() + injector.listenersNotifier.searchListeners.add(searchWidgetListener) + injector.listenersNotifier.vimPluginListeners.add(searchWidgetListener) + val macroWidgetListener = MacroWidgetListener() injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener) injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener) @@ -205,8 +211,10 @@ internal object VimListenerManager { // This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case optionGroup.addGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener) optionGroup.addGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener) + optionGroup.addGlobalOptionChangeListener(Options.showmode, searchWidgetOptionListener) modeWidgetOptionListener.onGlobalOptionChanged() macroWidgetOptionListener.onGlobalOptionChanged() + searchWidgetOptionListener.onGlobalOptionChanged() // Listen for and initialise new editors EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable) @@ -224,6 +232,7 @@ internal object VimListenerManager { optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener) optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener) optionGroup.removeGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener) + optionGroup.removeGlobalOptionChangeListener(Options.showmode, searchWidgetOptionListener) optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener) } } diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt index 38948c1b4f..c01b770b6a 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimSearchGroup.kt @@ -116,6 +116,7 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent() + statusBarWidgetsManager.updateWidget(factory) + } +} + +class VimSearchWidget : StatusBarWidget, VimStatusBarWidget { + var content: String = "" + + override fun ID(): String { + return ID + } + + override fun getPresentation(): StatusBarWidget.WidgetPresentation { + return VimModeWidgetPresentation() + } + + private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation { + override fun getAlignment(): Float = Component.CENTER_ALIGNMENT + + override fun getText(): String { + return content + } + + override fun getTooltipText(): String { + return content.ifEmpty { + "No search in progress" + } + } + } +} + +class SearchWidgetListener : SearchListener, VimWidgetListener({ updateSearchWidget() }) { + + override fun searchUpdated(editor: VimEditor, offset: Int) { + val ijEditor = editor.ij + var currentHighlighter = 1 + val numHighlighters = ijEditor.vimLastHighlighters?.size ?: 0 + for (highlighter in ijEditor.vimLastHighlighters!!) { + if (highlighter.startOffset == offset) { + break + } + currentHighlighter++ + } + for (project in ProjectManager.getInstance().openProjects) { + val searchWidget = getWidget(project) ?: continue + searchWidget.content = String.format("Occurrence: %s of %s", currentHighlighter, numHighlighters) + searchWidget.updateWidgetInStatusBar(ID, project) + } + } + + override fun searchStopped() { + for (project in ProjectManager.getInstance().openProjects) { + val searchWidget = getWidget(project) ?: continue + searchWidget.content = "" + searchWidget.updateWidgetInStatusBar(ID, project) + } + } + + private fun getWidget(project: Project): VimSearchWidget? { + val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null + return statusBar.getWidget(ID) as? VimSearchWidget + } + +} + +val searchWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateSearchWidget() } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 91e9acf986..5738297a1d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -62,6 +62,7 @@ + diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt index 5f24b35e7d..99bb0161d6 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimSearchGroupBase.kt @@ -1340,6 +1340,7 @@ abstract class VimSearchGroupBase : VimSearchGroup { } else if (lastPatternTrailing!![ppos + 1] == '?') { Direction.BACKWARDS } else { + injector.listenersNotifier.notifySearchUpdated(editor, res) return if (res == -1) null else Pair(res, motionType) } if (lastPatternTrailing!!.length - ppos > 2) { @@ -1347,6 +1348,7 @@ abstract class VimSearchGroupBase : VimSearchGroup { } res = processSearchCommand(editor, lastPatternTrailing!!.substring(ppos + 1), res, 1, nextDir)?.first ?: -1 } + injector.listenersNotifier.notifySearchUpdated(editor, res) return if (res == -1) null else Pair(res, motionType) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/SearchListener.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/SearchListener.kt new file mode 100644 index 0000000000..af318a312d --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/SearchListener.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.common + +import com.maddyhome.idea.vim.api.VimEditor + +interface SearchListener { + + fun searchUpdated(editor: VimEditor, offset: Int) + fun searchStopped() + +} \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt index bbbb0b3a32..906e7c8ece 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt @@ -20,6 +20,7 @@ class VimListenersNotifier { val modeChangeListeners: MutableCollection = ConcurrentLinkedDeque() val myEditorListeners: MutableCollection = ConcurrentLinkedDeque() val macroRecordingListeners: MutableCollection = ConcurrentLinkedDeque() + val searchListeners: MutableCollection = ConcurrentLinkedDeque() val vimPluginListeners: MutableCollection = ConcurrentLinkedDeque() val isReplaceCharListeners: MutableCollection = ConcurrentLinkedDeque() val yankListeners: MutableCollection = ConcurrentLinkedDeque() @@ -49,6 +50,16 @@ class VimListenersNotifier { myEditorListeners.forEach { it.focusLost(editor) } } + fun notifySearchUpdated(editor: VimEditor, offset: Int) { + if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case + searchListeners.forEach { it.searchUpdated(editor, offset) } + } + + fun notifySearchStopped() { + if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case + searchListeners.forEach { it.searchStopped() } + } + fun notifyMacroRecordingStarted() { if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case macroRecordingListeners.forEach { it.recordingStarted() }