Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,18 +40,12 @@ intellijPlatform {
changeNotes = """
<h2>Version $currentVersion</h2>
<ul>
<li>Add a fallback solution for file path handling on Windows
<li>
Implement Fuzzy File Search (Open Tabs) action
<ul>
<li>Thanks to <a href="https://github.com/s0ders">s0ders</a>!</li>
<li><code>com.mituuz.fuzzier.search.FuzzierOpenTabs</code></li>
</ul>
</li>
<li>Add a built-in Fuzzier grep backend
<ul>
<li>Replaces <code>grep</code> and <code>findstr</code> fallback implementations</li>
<li>Add setting to choose between Dynamic (uses <code>rg</code> if available, otherwise Fuzzier) and Fuzzier backends</li>
</ul>
</li>
<li>Migrate from <code>Timer</code> to coroutines for debouncing</li>
</ul>
""".trimIndent()

Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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
Expand All @@ -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)
)
}

Expand Down Expand Up @@ -99,9 +86,7 @@ abstract class FilesystemAction : FuzzyAction() {
val processedFiles = ConcurrentHashMap.newKeySet<String>()
val listLimit = fileListLimit
val priorityQueue = PriorityQueue(
listLimit + 1,
compareBy<FuzzyMatchContainer> { it.getScore(prioritizeShorterDirPaths) }
)
listLimit + 1, compareBy<FuzzyMatchContainer> { it.getScore(prioritizeShorterDirPaths) })

val queueLock = Any()
var minimumScore: Int? = null
Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.*
Expand All @@ -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]
Expand Down Expand Up @@ -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()
)
}

Expand Down
16 changes: 0 additions & 16 deletions src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pair<FileIndex, String>>,
project: Project,
shouldContinue: () -> Boolean,
fileFilter: (VirtualFile) -> Boolean
): List<IterationEntry> = buildList {
for ((fileIndex, moduleName) in targets) {
val targetIndexes = getTargetIndexes(project)

for ((fileIndex, moduleName) in targetIndexes) {
fileIndex.iterateContent { vf ->
if (!shouldContinue()) return@iterateContent false

Expand All @@ -47,4 +54,13 @@ class IntelliJIterationFileCollector : IterationFileCollector {
}
}
}

private fun getTargetIndexes(project: Project): List<Pair<FileIndex, String>> {
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 }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pair<FileIndex, String>>,
project: Project,
shouldContinue: () -> Boolean,
fileFilter: (VirtualFile) -> Boolean,
): List<IterationEntry>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IterationEntry> = buildList {
val fileEditorManager = FileEditorManager.getInstance(project)
val projectFileIndex = ProjectFileIndex.getInstance(project)

ReadAction.run<Throwable> {
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)
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/mituuz/fuzzier/operation/FuzzyMover.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
}
Expand Down
Loading
Loading