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
- - Add a fallback solution for file path handling on Windows
+
-
+ Implement Fuzzy File Search (Open Tabs) action
- - Thanks to s0ders!
+ com.mituuz.fuzzier.search.FuzzierOpenTabs
- - Add a built-in Fuzzier grep backend
-
- - Replaces
grep and findstr fallback implementations
- - Add setting to choose between Dynamic (uses
rg if available, otherwise Fuzzier) and Fuzzier backends
-
-
- - Migrate from
Timer to coroutines for debouncing
""".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/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"
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..6417e43f 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
@@ -41,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.*
@@ -68,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()
fun fill(settingsComponent: FuzzierGlobalSettingsComponent) {
val project = ProjectManager.getInstance().openProjects[0]
@@ -222,15 +217,9 @@ 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 }
- }
-
+ val collector = IntelliJIterationFileCollector(projectState)
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">
+
+
+
{
- 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/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
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..38bff3cf
--- /dev/null
+++ b/src/test/kotlin/com/mituuz/fuzzier/intellij/iteration/OpenTabsCollectorTest.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.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.Assertions.assertTrue
+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)
+ 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
+ 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