From 7cddb9819369fa98e5335bff6f551212d363c599 Mon Sep 17 00:00:00 2001 From: Mitja Date: Sat, 7 Feb 2026 10:01:36 +0200 Subject: [PATCH 1/5] Implement fuzzy search for open tabs only --- build.gradle.kts | 14 ++--- changelog.md | 5 ++ readme.md | 8 ++- .../com/mituuz/fuzzier/actions/FuzzyAction.kt | 17 ++++++ .../actions/filesystem/FilesystemAction.kt | 31 +++-------- .../fuzzier/components/TestBenchComponent.kt | 14 +---- .../com/mituuz/fuzzier/grep/FuzzyGrep.kt | 16 ------ .../IntelliJIterationFileCollector.kt | 22 +++++++- .../iteration/IterationFileCollector.kt | 4 +- .../intellij/iteration/OpenTabsCollector.kt | 55 +++++++++++++++++++ .../mituuz/fuzzier/operation/FuzzyMover.kt | 6 ++ .../com/mituuz/fuzzier/search/Fuzzier.kt | 8 ++- .../mituuz/fuzzier/search/FuzzierOpenTabs.kt | 36 ++++++++++++ src/main/resources/META-INF/plugin.xml | 7 +++ 14 files changed, 173 insertions(+), 70 deletions(-) create mode 100644 src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollector.kt create mode 100644 src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1b47f552..92b5c0fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile // Use the same version and group for the jar and the plugin -val currentVersion = "2.1.0" +val currentVersion = "2.2.0" val myGroup = "com.mituuz" version = currentVersion group = myGroup @@ -40,18 +40,12 @@ intellijPlatform { changeNotes = """

Version $currentVersion

""".trimIndent() diff --git a/changelog.md b/changelog.md index 9e14a29e..3cd051b9 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## Version 2.2.0 + +- Implement Fuzzy File Search (Open Tabs) action + - `com.mituuz.fuzzier.search.FuzzierOpenTabs` + ## Version 2.1.0 - Add a fallback solution for file path handling on Windows diff --git a/readme.md b/readme.md index 9733f3eb..22576af9 100644 --- a/readme.md +++ b/readme.md @@ -54,13 +54,17 @@ List movement can be remapped from settings -> keymaps, but do not support chord - **Fuzzier** (`com.mituuz.fuzzier.search.Fuzzier`) - Search files using a fuzzy search - **FuzzierVCS** (`com.mituuz.fuzzier.search.FuzzierVCS`) - Search files using a fuzzy search on only VCS tracked files +- **FuzzierOpenTabs** (`com.mituuz.fuzzier.search.FuzzierOpenTabs`) - Search files using a fuzzy search on currently + open tabs - **FuzzyMover** (`com.mituuz.fuzzier.operation.FuzzyMover`) - Move a file using a fuzzy search - **FuzzyGrep** (`com.mituuz.fuzzier.grep.FuzzyGrep`) - Case sensitive text search from project - **FuzzyGrepCI** (`com.mituuz.fuzzier.grep.FuzzyGrepCI`) - Case insensitive text search from project - **FuzzyGrepOpenTabs** (`com.mituuz.fuzzier.grep.FuzzyGrepOpenTabs`) - Case sensitive text search from open tabs - **FuzzyGrepOpenTabsCI** (`com.mituuz.fuzzier.grep.FuzzyGrepOpenTabsCI`) - Case insensitive text search from open tabs -- **FuzzyGrepCurrentBuffer** (`com.mituuz.fuzzier.grep.FuzzyGrepCurrentBuffer`) - Case sensitive text search from current buffer -- **FuzzyGrepCurrentBufferCI** (`com.mituuz.fuzzier.grep.FuzzyGrepCurrentBufferCI`) - Case insensitive text search from current buffer +- **FuzzyGrepCurrentBuffer** (`com.mituuz.fuzzier.grep.FuzzyGrepCurrentBuffer`) - Case sensitive text search from + current buffer +- **FuzzyGrepCurrentBufferCI** (`com.mituuz.fuzzier.grep.FuzzyGrepCurrentBufferCI`) - Case insensitive text search from + current buffer ## Documentation diff --git a/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt b/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt index ac4d0b73..0de63db7 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt @@ -24,6 +24,9 @@ package com.mituuz.fuzzier.actions import com.intellij.icons.AllIcons +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications import com.intellij.openapi.actionSystem.* import com.intellij.openapi.components.service import com.intellij.openapi.editor.Caret @@ -57,6 +60,10 @@ import java.util.concurrent.ConcurrentHashMap import javax.swing.* abstract class FuzzyAction : AnAction() { + companion object { + const val FUZZIER_NOTIFICATION_GROUP: String = "Fuzzier Notification Group" + } + lateinit var component: FuzzyComponent lateinit var popup: JBPopup private var originalDownHandler: EditorActionHandler? = null @@ -71,6 +78,16 @@ abstract class FuzzyAction : AnAction() { protected open var currentUpdateListContentJob: Job? = null protected open var actionScope: CoroutineScope? = null + protected fun showNotification( + title: String, content: String, project: Project, type: NotificationType = NotificationType.ERROR + ) { + val grepNotification = Notification( + FUZZIER_NOTIFICATION_GROUP, title, content, type + ) + Notifications.Bus.notify(grepNotification, project) + } + + protected fun getPopupProvider(): PopupProvider { return when (globalState.popupSizing) { FuzzierGlobalSettingsService.PopupSizing.AUTO_SIZE -> { diff --git a/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt b/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt index 94ba0f6f..cce75ecb 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt @@ -26,14 +26,10 @@ package com.mituuz.fuzzier.actions.filesystem import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.EDT -import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project -import com.intellij.openapi.project.rootManager -import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.actions.FuzzyAction import com.mituuz.fuzzier.entities.* -import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.util.FuzzierUtil import kotlinx.coroutines.* @@ -43,11 +39,10 @@ import java.util.concurrent.ConcurrentHashMap import javax.swing.DefaultListModel abstract class FilesystemAction : FuzzyAction() { - private var collector: IterationFileCollector = IntelliJIterationFileCollector() + abstract fun createCollector(): IterationFileCollector abstract override fun runAction( - project: Project, - actionEvent: AnActionEvent + project: Project, actionEvent: AnActionEvent ) abstract fun buildFileFilter(project: Project): (VirtualFile) -> Boolean @@ -58,17 +53,9 @@ abstract class FilesystemAction : FuzzyAction() { val ctx = currentCoroutineContext() val job = ctx.job - val indexTargets = if (projectState.isProject) { - listOf(ProjectFileIndex.getInstance(project) to project.name) - } else { - val moduleManager = ModuleManager.getInstance(project) - moduleManager.modules.map { it.rootManager.fileIndex to it.name } - } - - return collector.collectFiles( - targets = indexTargets, - shouldContinue = { job.isActive }, - fileFilter = buildFileFilter(project) + val c: IterationFileCollector = createCollector() + return c.collectFiles( + project = project, shouldContinue = { job.isActive }, fileFilter = buildFileFilter(project) ) } @@ -99,9 +86,7 @@ abstract class FilesystemAction : FuzzyAction() { val processedFiles = ConcurrentHashMap.newKeySet() val listLimit = fileListLimit val priorityQueue = PriorityQueue( - listLimit + 1, - compareBy { it.getScore(prioritizeShorterDirPaths) } - ) + listLimit + 1, compareBy { it.getScore(prioritizeShorterDirPaths) }) val queueLock = Any() var minimumScore: Int? = null @@ -139,9 +124,7 @@ abstract class FilesystemAction : FuzzyAction() { } } - fileEntries - .filter { processedFiles.add(it.path) } - .forEach { ch.send(it) } + fileEntries.filter { processedFiles.add(it.path) }.forEach { ch.send(it) } ch.close() } diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index 9103ea8f..690d0895 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -28,11 +28,8 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener -import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager -import com.intellij.openapi.project.rootManager -import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.EditorTextField import com.intellij.ui.components.JBScrollPane @@ -68,7 +65,7 @@ class TestBenchComponent : JPanel(), Disposable { private lateinit var projectState: FuzzierSettingsService.State private var currentUpdateListContentJob: Job? = null private var actionScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private var collector: IterationFileCollector = IntelliJIterationFileCollector() + private var collector: IterationFileCollector = IntelliJIterationFileCollector(projectState) fun fill(settingsComponent: FuzzierGlobalSettingsComponent) { val project = ProjectManager.getInstance().openProjects[0] @@ -222,15 +219,8 @@ class TestBenchComponent : JPanel(), Disposable { val ctx = currentCoroutineContext() val job = ctx.job - val indexTargets = if (projectState.isProject) { - listOf(ProjectFileIndex.getInstance(project) to project.name) - } else { - val moduleManager = ModuleManager.getInstance(project) - moduleManager.modules.map { it.rootManager.fileIndex to it.name } - } - return collector.collectFiles( - targets = indexTargets, shouldContinue = { job.isActive }, fileFilter = buildFileFilter() + project = project, shouldContinue = { job.isActive }, fileFilter = buildFileFilter() ) } diff --git a/src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt b/src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt index 8159b247..fcddc9bd 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt @@ -24,9 +24,6 @@ package com.mituuz.fuzzier.grep -import com.intellij.notification.Notification -import com.intellij.notification.NotificationType -import com.intellij.notification.Notifications import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.EDT @@ -59,10 +56,6 @@ import javax.swing.DefaultListModel import javax.swing.ListModel open class FuzzyGrep : FuzzyAction() { - companion object { - const val FUZZIER_NOTIFICATION_GROUP: String = "Fuzzier Notification Group" - } - val isWindows = System.getProperty("os.name").lowercase().contains("win") private val backendResolver = BackendResolver(isWindows) private val commandRunner = CommandRunner() @@ -129,15 +122,6 @@ open class FuzzyGrep : FuzzyAction() { } } - private fun showNotification( - title: String, content: String, project: Project, type: NotificationType = NotificationType.ERROR - ) { - val grepNotification = Notification( - FUZZIER_NOTIFICATION_GROUP, title, content, type - ) - Notifications.Bus.notify(grepNotification, project) - } - override fun onPopupClosed() { previewAlarm?.dispose() currentLaunchJob?.cancel() diff --git a/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollector.kt b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollector.kt index 991cca57..cca47634 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollector.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollector.kt @@ -24,17 +24,24 @@ package com.mituuz.fuzzier.intellij.iteration +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager import com.intellij.openapi.roots.FileIndex +import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.entities.IterationEntry +import com.mituuz.fuzzier.settings.FuzzierSettingsService -class IntelliJIterationFileCollector : IterationFileCollector { +class IntelliJIterationFileCollector(val projectState: FuzzierSettingsService.State) : IterationFileCollector { override fun collectFiles( - targets: List>, + project: Project, shouldContinue: () -> Boolean, fileFilter: (VirtualFile) -> Boolean ): List = buildList { - for ((fileIndex, moduleName) in targets) { + val targetIndexes = getTargetIndexes(project) + + for ((fileIndex, moduleName) in targetIndexes) { fileIndex.iterateContent { vf -> if (!shouldContinue()) return@iterateContent false @@ -47,4 +54,13 @@ class IntelliJIterationFileCollector : IterationFileCollector { } } } + + private fun getTargetIndexes(project: Project): List> { + return if (projectState.isProject) { + listOf(ProjectFileIndex.getInstance(project) to project.name) + } else { + val moduleManager = ModuleManager.getInstance(project) + moduleManager.modules.map { it.rootManager.fileIndex to it.name } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IterationFileCollector.kt b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IterationFileCollector.kt index b142c938..07f1d471 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IterationFileCollector.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/IterationFileCollector.kt @@ -24,13 +24,13 @@ package com.mituuz.fuzzier.intellij.iteration -import com.intellij.openapi.roots.FileIndex +import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.entities.IterationEntry interface IterationFileCollector { fun collectFiles( - targets: List>, + project: Project, shouldContinue: () -> Boolean, fileFilter: (VirtualFile) -> Boolean, ): List diff --git a/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollector.kt b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollector.kt new file mode 100644 index 00000000..9239ea38 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollector.kt @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.intellij.iteration + +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VirtualFile +import com.mituuz.fuzzier.entities.IterationEntry + +class OpenTabsCollector : IterationFileCollector { + override fun collectFiles( + project: Project, + shouldContinue: () -> Boolean, + fileFilter: (VirtualFile) -> Boolean + ): List = buildList { + val fileEditorManager = FileEditorManager.getInstance(project) + val projectFileIndex = ProjectFileIndex.getInstance(project) + + ReadAction.run { + for (vf in fileEditorManager.openFiles) { + if (!shouldContinue()) return@run + if (fileFilter(vf)) { + val module = projectFileIndex.getModuleForFile(vf) + val moduleName = module?.name ?: "" + val iteratorEntry = IterationEntry(vf.name, vf.path, moduleName, vf.isDirectory) + add(iteratorEntry) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/operation/FuzzyMover.kt b/src/main/kotlin/com/mituuz/fuzzier/operation/FuzzyMover.kt index 9943b0ad..1c8244c0 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/operation/FuzzyMover.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/operation/FuzzyMover.kt @@ -39,6 +39,8 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.mituuz.fuzzier.actions.filesystem.FilesystemAction import com.mituuz.fuzzier.components.SimpleFinderComponent +import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector +import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.ui.bindings.ActivationBindings import com.mituuz.fuzzier.ui.popup.PopupConfig import javax.swing.DefaultListModel @@ -47,6 +49,10 @@ class FuzzyMover : FilesystemAction() { lateinit var movableFile: PsiFile lateinit var currentFile: VirtualFile + override fun createCollector(): IterationFileCollector { + return IntelliJIterationFileCollector(projectState) + } + override fun buildFileFilter(project: Project): (VirtualFile) -> Boolean { return { vf -> if (component.isDirSelector) vf.isDirectory else !vf.isDirectory } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index 8432d556..d79d2e90 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -37,6 +37,8 @@ import com.mituuz.fuzzier.actions.filesystem.FilesystemAction import com.mituuz.fuzzier.components.FuzzyFinderComponent import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.intellij.files.FileOpeningUtil +import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector +import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.ui.bindings.ActivationBindings import com.mituuz.fuzzier.ui.popup.PopupConfig @@ -48,6 +50,10 @@ open class Fuzzier : FilesystemAction() { private var lastPreviewKey: String? = null protected open var popupTitle = "Fuzzy Search" + override fun createCollector(): IterationFileCollector { + return IntelliJIterationFileCollector(projectState) + } + override fun buildFileFilter(project: Project): (VirtualFile) -> Boolean = { vf -> !vf.isDirectory } @@ -141,7 +147,7 @@ open class Fuzzier : FilesystemAction() { val editorHistoryManager = EditorHistoryManager.getInstance(project) val listModel = when (globalState.recentFilesMode) { - FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> InitialViewHandler.Companion.getRecentProjectFiles( + FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> InitialViewHandler.getRecentProjectFiles( globalState, fuzzierUtil, editorHistoryManager, diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt new file mode 100644 index 00000000..5cc17de3 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search + +import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector +import com.mituuz.fuzzier.intellij.iteration.OpenTabsCollector + +class FuzzierOpenTabs : Fuzzier() { + override var popupTitle: String = "Fuzzy Search (Open Tabs)" + + override fun createCollector(): IterationFileCollector { + return OpenTabsCollector() + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 82d03a99..2d58fdd3 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -93,6 +93,13 @@ description="Search files using a fuzzy search"> + + + Date: Sat, 7 Feb 2026 10:12:17 +0200 Subject: [PATCH 2/5] Fix tests --- .../fuzzier/components/TestBenchComponent.kt | 3 +- .../IntelliJIteratorEntryCollectorTest.kt | 148 ------------------ 2 files changed, 1 insertion(+), 150 deletions(-) delete mode 100644 src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIteratorEntryCollectorTest.kt diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index 690d0895..6417e43f 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -38,7 +38,6 @@ import com.intellij.uiDesigner.core.GridConstraints import com.intellij.uiDesigner.core.GridLayoutManager import com.mituuz.fuzzier.entities.* import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector -import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.util.FuzzierUtil import kotlinx.coroutines.* @@ -65,7 +64,6 @@ class TestBenchComponent : JPanel(), Disposable { private lateinit var projectState: FuzzierSettingsService.State private var currentUpdateListContentJob: Job? = null private var actionScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private var collector: IterationFileCollector = IntelliJIterationFileCollector(projectState) fun fill(settingsComponent: FuzzierGlobalSettingsComponent) { val project = ProjectManager.getInstance().openProjects[0] @@ -219,6 +217,7 @@ class TestBenchComponent : JPanel(), Disposable { val ctx = currentCoroutineContext() val job = ctx.job + val collector = IntelliJIterationFileCollector(projectState) return collector.collectFiles( project = project, shouldContinue = { job.isActive }, fileFilter = buildFileFilter() ) diff --git a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIteratorEntryCollectorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIteratorEntryCollectorTest.kt deleted file mode 100644 index de7eca70..00000000 --- a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIteratorEntryCollectorTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2025 Mitja Leino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.mituuz.fuzzier.intellij.iteration - -import com.intellij.openapi.roots.ContentIterator -import com.intellij.openapi.roots.FileIndex -import com.intellij.testFramework.LightVirtualFile -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.util.concurrent.atomic.AtomicInteger - -class IntelliJIteratorEntryCollectorTest { - private lateinit var collector: IntelliJIterationFileCollector - - @BeforeEach - fun setUp() { - collector = IntelliJIterationFileCollector() - } - - @Test - fun `collectFiles returns empty list when targets are empty`() { - val result = collector.collectFiles( - targets = emptyList(), - shouldContinue = { true }, - fileFilter = { true }, - ) - assertTrue(result.isEmpty()) - } - - @Test - fun `stops iterating when shouldContinue becomes false`() { - val file1 = LightVirtualFile("a.txt") - val file2 = LightVirtualFile("b.txt") - val index = mockk() - val iteratorSlot = slot() - - every { index.iterateContent(capture(iteratorSlot)) } answers { - iteratorSlot.captured.processFile(file1) - iteratorSlot.captured.processFile(file2) - true - } - - val calls = AtomicInteger(0) - val res = collector.collectFiles( - targets = listOf(index to "mod"), - shouldContinue = { calls.incrementAndGet() == 1 }, - fileFilter = { true } - ) - - assertEquals(1, res.size) - assertEquals("a.txt", res[0].name) - } - - @Test - fun `skips files that match filter`() { - val file1 = LightVirtualFile("a.txt") - val dir = mockk() - val file2 = LightVirtualFile("b.txt") - val index = mockk() - val iteratorSlot = slot() - - every { dir.isDirectory } returns true - every { index.iterateContent(capture(iteratorSlot)) } answers { - iteratorSlot.captured.processFile(file1) - iteratorSlot.captured.processFile(dir) - iteratorSlot.captured.processFile(file2) - true - } - - val res = collector.collectFiles( - targets = listOf(index to "mod"), - shouldContinue = { true }, - fileFilter = { vf -> !vf.isDirectory } - ) - - assertEquals(2, res.size) - assertEquals("a.txt", res[0].name) - assertEquals("b.txt", res[1].name) - } - - @Test - fun `collects files from multiple file indexes`() { - val file1 = LightVirtualFile("a.txt") - val file2 = LightVirtualFile("b.txt") - val file3 = LightVirtualFile("c.txt") - val file4 = LightVirtualFile("d.txt") - - val index1 = mockk() - val index2 = mockk() - val iteratorSlot1 = slot() - val iteratorSlot2 = slot() - - every { index1.iterateContent(capture(iteratorSlot1)) } answers { - iteratorSlot1.captured.processFile(file1) - iteratorSlot1.captured.processFile(file2) - true - } - - every { index2.iterateContent(capture(iteratorSlot2)) } answers { - iteratorSlot2.captured.processFile(file3) - iteratorSlot2.captured.processFile(file4) - true - } - - val res = collector.collectFiles( - targets = listOf(index1 to "mod1", index2 to "mod2"), - shouldContinue = { true }, - fileFilter = { true } - ) - - assertEquals(4, res.size) - assertEquals("a.txt", res[0].name) - assertEquals("mod1", res[0].module) - assertEquals("b.txt", res[1].name) - assertEquals("mod1", res[1].module) - assertEquals("c.txt", res[2].name) - assertEquals("mod2", res[2].module) - assertEquals("d.txt", res[3].name) - assertEquals("mod2", res[3].module) - } -} \ No newline at end of file From 8df2ccee58f04030c9b68dcbaa4f2bb3990bdcd6 Mon Sep 17 00:00:00 2001 From: Mitja Date: Sat, 7 Feb 2026 10:55:17 +0200 Subject: [PATCH 3/5] Update IntelliJ platform plugin --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 876062a3..467dfb4c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ junit5 = "6.0.1" junit4 = "4.13.2" kotlin = "2.2.21" -intellijPlatform = "2.10.5" +intellijPlatform = "2.11.0" kover = "0.9.3" communityVersion = "2025.1" mockk = "1.14.6" From 2b821107c03a63433750772d7026f79db3c0c334 Mon Sep 17 00:00:00 2001 From: Mitja Date: Sat, 7 Feb 2026 11:11:51 +0200 Subject: [PATCH 4/5] Add tests for tab collector --- .../iteration/OpenTabsCollectorTest.kt | 121 ++++++++++++++++++ .../fuzzier/search/BackendResolverTest.kt | 1 - 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt diff --git a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt new file mode 100644 index 00000000..dacd7bee --- /dev/null +++ b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt @@ -0,0 +1,121 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.intellij.iteration + +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.runInEdtAndWait +import com.mituuz.fuzzier.TestUtil +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class OpenTabsCollectorTest { + @Suppress("unused") + private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance() + private val testUtil = TestUtil() + private lateinit var fixture: CodeInsightTestFixture + private lateinit var openTabsCollector: OpenTabsCollector + + @BeforeEach + fun setup() { + fixture = testUtil.setUpProject(listOf("src/file1.txt", "src/file2.txt", "src/file3.txt")) + openTabsCollector = OpenTabsCollector() + } + + @AfterEach + fun tearDown() { + fixture.tearDown() + } + + @Test + fun `collectFiles should collect all open files`() { + val project = fixture.project + val fileEditorManager = FileEditorManager.getInstance(project) + val file1 = fixture.findFileInTempDir("src/file1.txt") + val file2 = fixture.findFileInTempDir("src/file2.txt") + + runInEdtAndWait { + runReadAction { + fileEditorManager.openFile(file1, true) + fileEditorManager.openFile(file2, true) + } + } + + val result = openTabsCollector.collectFiles(project, { true }, { true }) + + assertEquals(2, result.size) + assertEquals("file1.txt", result[0].name) + assertEquals("light_idea_test_case", result[0].module) + assertEquals("file2.txt", result[1].name) + assertEquals("light_idea_test_case", result[1].module) + } + + @Test + fun `collectFiles should respect fileFilter`() { + val project = fixture.project + val fileEditorManager = FileEditorManager.getInstance(project) + val file1 = fixture.findFileInTempDir("src/file1.txt") + val file2 = fixture.findFileInTempDir("src/file2.txt") + + runInEdtAndWait { + runReadAction { + fileEditorManager.openFile(file1, true) + fileEditorManager.openFile(file2, true) + } + } + + val result = openTabsCollector.collectFiles(project, { true }, { it.name == "file1.txt" }) + + assertEquals(1, result.size) + assertEquals("file1.txt", result[0].name) + } + + @Test + fun `collectFiles should respect shouldContinue`() { + val project = fixture.project + val fileEditorManager = FileEditorManager.getInstance(project) + val file1 = fixture.findFileInTempDir("src/file1.txt") + val file2 = fixture.findFileInTempDir("src/file2.txt") + + runInEdtAndWait { + runReadAction { + fileEditorManager.openFile(file1, true) + fileEditorManager.openFile(file2, true) + } + } + + var count = 0 + val result = openTabsCollector.collectFiles(project, { + count++ + count <= 1 + }, { true }) + + assertEquals(1, result.size) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/BackendResolverTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/BackendResolverTest.kt index 818b32d6..d9ad9957 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/BackendResolverTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/BackendResolverTest.kt @@ -26,7 +26,6 @@ package com.mituuz.fuzzier.search import com.intellij.openapi.application.ApplicationManager import com.intellij.testFramework.TestApplicationManager -import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.mituuz.fuzzier.grep.backend.BackendResolver import com.mituuz.fuzzier.grep.backend.FuzzierGrep import com.mituuz.fuzzier.grep.backend.Ripgrep From 85448d6d0852deae85bc4dc20330481340b0eb47 Mon Sep 17 00:00:00 2001 From: Mitja Date: Sat, 7 Feb 2026 11:35:10 +0200 Subject: [PATCH 5/5] Add tests for base collector --- .../kotlin/com/mituuz/fuzzier/TestUtil.kt | 7 +- .../IntelliJIterationFileCollectorTest.kt | 122 ++++++++++++++++++ .../iteration/OpenTabsCollectorTest.kt | 7 +- 3 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollectorTest.kt diff --git a/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt b/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt index 6c8722ca..05a65e0d 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt @@ -108,7 +108,7 @@ class TestUtil { val modulePath = myFixture.findFileInTempDir(moduleFiles[0]) val module = WriteAction.computeAndWait { - ModuleManager.getInstance(project).newModule(modulePath.path, "Empty") + ModuleManager.getInstance(project).newModule(modulePath.path, moduleFiles[0]) } PsiTestUtil.addSourceRoot(module, modulePath) } @@ -139,6 +139,11 @@ class TestUtil { private fun addFiles(files: List, myFixture: CodeInsightTestFixture) { for (file in files) { + val parts = file.split("/") + if (parts.size > 1) { + val dir = parts.dropLast(1).joinToString("/") + myFixture.tempDirFixture.findOrCreateDir(dir) + } myFixture.addFileToProject(file, "") } } diff --git a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollectorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollectorTest.kt new file mode 100644 index 00000000..19fce8d1 --- /dev/null +++ b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/IntelliJIterationFileCollectorTest.kt @@ -0,0 +1,122 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.intellij.iteration + +import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.mituuz.fuzzier.TestUtil +import com.mituuz.fuzzier.settings.FuzzierSettingsService +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class IntelliJIterationFileCollectorTest { + @Suppress("unused") + private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance() + private val testUtil = TestUtil() + private lateinit var fixture: CodeInsightTestFixture + private lateinit var projectState: FuzzierSettingsService.State + private lateinit var collector: IntelliJIterationFileCollector + + @BeforeEach + fun setup() { + projectState = FuzzierSettingsService.State() + collector = IntelliJIterationFileCollector(projectState) + } + + @AfterEach + fun tearDown() { + if (::fixture.isInitialized) { + fixture.tearDown() + } + } + + @Test + fun `collectFiles should collect all files in project mode`() { + fixture = testUtil.setUpProject(listOf("src/file1.txt", "src/file2.txt")) + projectState.isProject = true + + val result = collector.collectFiles( + project = fixture.project, + shouldContinue = { true }, + fileFilter = { !it.isDirectory } + ) + + assertEquals(2, result.size) + assertTrue(result.any { it.name == "file1.txt" }) + assertTrue(result.any { it.name == "file2.txt" }) + assertEquals("Test", result[0].module) + } + + @Test + fun `collectFiles should collect files in module mode`() { + fixture = testUtil.setUpDuoModuleProject(listOf("src1/file1.txt"), listOf("src2/file2.txt")) + projectState.isProject = false + + val result = collector.collectFiles( + project = fixture.project, + shouldContinue = { true }, + fileFilter = { !it.isDirectory } + ) + + assertEquals(2, result.size) + val file1 = result.find { it.name == "file1.txt" } + val file2 = result.find { it.name == "file2.txt" } + + assertEquals("src1", file1?.module) + assertEquals("src2", file2?.module) + } + + @Test + fun `collectFiles should respect fileFilter`() { + fixture = testUtil.setUpProject(listOf("src/file1.txt", "src/file2.txt")) + projectState.isProject = true + + val result = collector.collectFiles( + project = fixture.project, + shouldContinue = { true }, + fileFilter = { it.name == "file1.txt" } + ) + + assertEquals(1, result.size) + assertEquals("file1.txt", result[0].name) + } + + @Test + fun `collectFiles should respect shouldContinue`() { + fixture = testUtil.setUpProject(listOf("src/file1.txt", "src/file2.txt", "src/file3.txt")) + projectState.isProject = true + + val result = collector.collectFiles( + project = fixture.project, + shouldContinue = { false }, + fileFilter = { !it.isDirectory } + ) + + assertTrue(result.isEmpty()) + } +} diff --git a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt index dacd7bee..38bff3cf 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.kt @@ -32,6 +32,7 @@ import com.intellij.testFramework.runInEdtAndWait import com.mituuz.fuzzier.TestUtil import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -70,10 +71,10 @@ class OpenTabsCollectorTest { val result = openTabsCollector.collectFiles(project, { true }, { true }) assertEquals(2, result.size) - assertEquals("file1.txt", result[0].name) - assertEquals("light_idea_test_case", result[0].module) - assertEquals("file2.txt", result[1].name) + assertTrue(result.any { it.name == "file1.txt" }) + assertTrue(result.any { it.name == "file2.txt" }) assertEquals("light_idea_test_case", result[1].module) + assertEquals("light_idea_test_case", result[0].module) } @Test