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: 6 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

// Use same version and group for the jar and the plugin
val currentVersion = "1.2.0"
val currentVersion = "1.3.0"
val myGroup = "com.mituuz"
version = currentVersion
group = myGroup
Expand All @@ -33,10 +33,10 @@ dependencies {
testFramework(TestFrameworkType.Platform)
}

testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.3")
testImplementation("org.mockito:mockito-core:5.14.2")

testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.3")

// Required to fix issue where JUnit5 Test Framework refers to JUnit4
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
Expand All @@ -63,10 +63,8 @@ intellijPlatform {

changeNotes = """
<h2>Version $currentVersion</h2>
- Make popup dimensions persistent across projects<br>
&emsp;- Popup dimensions are saved per screen bounds (location and size)<br>
- Improve popup location consistency (fixes right screen, left half issue)<br>
- Update kotlin-jvm and intellij-platform plugins to 2.1.0
- Add option to list recently searched files on popup open<br>
- Update some dependencies
""".trimIndent()

ideaVersion {
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## Version 1.3.0
- Add option to list recently searched files on popup open
- Update some dependencies

## Version 1.2.0
- Make popup dimensions persistent across projects
- Improve popup location consistency (fixes right screen, left half issue)
Expand Down
69 changes: 39 additions & 30 deletions src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.wm.WindowManager
import com.mituuz.fuzzier.components.FuzzyFinderComponent
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.StringEvaluator
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.NONE
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENTLY_SEARCHED_FILES
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENT_PROJECT_FILES
import com.mituuz.fuzzier.util.FuzzierUtil
import com.mituuz.fuzzier.util.FuzzierUtil.Companion.createDimensionKey
import com.mituuz.fuzzier.util.InitialViewHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
Expand All @@ -66,6 +70,7 @@ open class Fuzzier : FuzzyAction() {
private var defaultDoc: Document? = null
open var title: String = "Fuzzy Search"
private val fuzzyDimensionKey: String = "FuzzySearchPopup"

// Used by FuzzierVCS to check if files are tracked by the VCS
protected var changeListManager: ChangeListManager? = null

Expand Down Expand Up @@ -133,24 +138,19 @@ open class Fuzzier : FuzzyAction() {
*/
private fun createInitialView(project: Project) {
ApplicationManager.getApplication().executeOnPooledThread {
val editorHistory = EditorHistoryManager.getInstance(project).fileList
val listModel = DefaultListModel<FuzzyMatchContainer>()
val limit = fuzzierSettingsService.state.fileListLimit

// Start from the end of editor history (most recent file)
var i = editorHistory.size - 1
while (i >= 0 && listModel.size() < limit) {
val file = editorHistory[i]
val filePathAndModule = fuzzierUtil.removeModulePath(file.path)
// Don't add files that do not have a module path in the project
if (filePathAndModule.second == "") {
i--
continue
val editorHistoryManager = EditorHistoryManager.getInstance(project)

val listModel = when (fuzzierSettingsService.state.recentFilesMode) {
RECENT_PROJECT_FILES -> InitialViewHandler.getRecentProjectFiles(
fuzzierSettingsService,
fuzzierUtil,
editorHistoryManager
)

RECENTLY_SEARCHED_FILES -> InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService)
else -> {
DefaultListModel<FuzzyMatchContainer>()
}
val fuzzyMatchContainer =
FuzzyMatchContainer.createOrderedContainer(i, filePathAndModule.first, filePathAndModule.second, file.name)
listModel.addElement(fuzzyMatchContainer)
i--
}

ApplicationManager.getApplication().invokeLater {
Expand Down Expand Up @@ -205,7 +205,7 @@ open class Fuzzier : FuzzyAction() {
}
}
}

private fun handleEmptySearchString(project: Project) {
if (fuzzierSettingsService.state.recentFilesMode != NONE) {
createInitialView(project)
Expand All @@ -216,34 +216,40 @@ open class Fuzzier : FuzzyAction() {
}
}
}

private fun getStringEvaluator(): StringEvaluator {
return StringEvaluator(
fuzzierSettingsService.state.exclusionSet,
fuzzierSettingsService.state.modules,
changeListManager
)
}

private fun process(project: Project, stringEvaluator: StringEvaluator, searchString: String,
listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {

private fun process(
project: Project, stringEvaluator: StringEvaluator, searchString: String,
listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?
) {
val moduleManager = ModuleManager.getInstance(project)
if (fuzzierSettingsService.state.isProject) {
processProject(project, stringEvaluator, searchString, listModel, task)
} else {
processModules(moduleManager, stringEvaluator, searchString, listModel, task)
}
}

private fun processProject(project: Project, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {

private fun processProject(
project: Project, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?
) {
val filesToIterate = ConcurrentHashMap.newKeySet<FuzzierUtil.IterationFile>()
FuzzierUtil.fileIndexToIterationFile(filesToIterate, ProjectFileIndex.getInstance(project), project.name, task)
processFiles(filesToIterate, stringEvaluator, listModel, searchString, task)
}

private fun processModules(moduleManager: ModuleManager, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {
private fun processModules(
moduleManager: ModuleManager, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?
) {
val filesToIterate = ConcurrentHashMap.newKeySet<FuzzierUtil.IterationFile>()
for (module in moduleManager.modules) {
FuzzierUtil.fileIndexToIterationFile(filesToIterate, module.rootManager.fileIndex, module.name, task)
Expand Down Expand Up @@ -271,7 +277,7 @@ open class Fuzzier : FuzzyAction() {
}
}

private fun openFile(project: Project, virtualFile: VirtualFile) {
private fun openFile(project: Project, fuzzyMatchContainer: FuzzyMatchContainer?, virtualFile: VirtualFile) {
val fileEditorManager = FileEditorManager.getInstance(project)
val currentEditor = fileEditorManager.selectedTextEditor
val previousFile = currentEditor?.virtualFile
Expand All @@ -288,6 +294,9 @@ open class Fuzzier : FuzzyAction() {
}
}
}
if (fuzzyMatchContainer != null) {
InitialViewHandler.addFileToRecentlySearchedFiles(fuzzyMatchContainer, fuzzierSettingsService)
}
popup?.cancel()
}

Expand Down Expand Up @@ -324,7 +333,7 @@ open class Fuzzier : FuzzyAction() {
VirtualFileManager.getInstance().findFileByUrl("file://${selectedValue?.getFileUri()}")
// Open the file in the editor
virtualFile?.let {
openFile(project, it)
openFile(project, selectedValue, it)
}
}
}
Expand All @@ -341,7 +350,7 @@ open class Fuzzier : FuzzyAction() {
val virtualFile =
VirtualFileManager.getInstance().findFileByUrl("file://${selectedValue?.getFileUri()}")
virtualFile?.let {
openFile(project, it)
openFile(project, selectedValue, it)
}
}
})
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import com.intellij.psi.PsiManager
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import com.mituuz.fuzzier.components.SimpleFinderComponent
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.StringEvaluator
import com.mituuz.fuzzier.util.FuzzierUtil.Companion.createDimensionKey
import org.apache.commons.lang3.StringUtils
import java.awt.Point
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import com.intellij.ui.components.JBTextArea
import com.intellij.ui.table.JBTable
import com.intellij.uiDesigner.core.GridConstraints
import com.intellij.uiDesigner.core.GridLayoutManager
import com.mituuz.fuzzier.StringEvaluator
import com.mituuz.fuzzier.entities.StringEvaluator
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.settings.FuzzierSettingsService
import com.mituuz.fuzzier.util.FuzzierUtil
Expand Down
61 changes: 57 additions & 4 deletions src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,35 @@ SOFTWARE.
package com.mituuz.fuzzier.entities

import com.intellij.openapi.components.service
import com.intellij.util.xmlb.Converter
import com.intellij.util.xmlb.XmlSerializationException
import com.mituuz.fuzzier.settings.FuzzierConfiguration.END_STYLE_TAG
import com.mituuz.fuzzier.settings.FuzzierConfiguration.startStyleTag
import com.mituuz.fuzzier.settings.FuzzierSettingsService

class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filename: String, private var module: String = "") {
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.Base64
import javax.swing.DefaultListModel

class FuzzyMatchContainer(
val score: FuzzyScore,
var filePath: String,
var filename: String,
private var module: String = ""
) : Serializable {
@Transient
private var initialPath: String? = null

companion object {
fun createOrderedContainer(order: Int, filePath: String, initialPath:String, filename: String): FuzzyMatchContainer {
fun createOrderedContainer(
order: Int,
filePath: String,
initialPath: String,
filename: String
): FuzzyMatchContainer {
val fuzzyScore = FuzzyScore()
fuzzyScore.filenameScore = order
val fuzzyMatchContainer = FuzzyMatchContainer(fuzzyScore, filePath, filename)
Expand Down Expand Up @@ -105,7 +125,7 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen
FILENAME_WITH_PATH_STYLED("Filename with (path) styled")
}

class FuzzyScore {
class FuzzyScore : Serializable {
var streakScore = 0
var multiMatchScore = 0
var partialPathScore = 0
Expand All @@ -120,4 +140,37 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen
override fun toString(): String {
return "FuzzyMatchContainer: $filename, score: ${getScore()}, dir score: ${getScoreWithDirLength()}"
}

/**
* This is necessary to persists recently used files between IDE restarts
*
* Uses a base 64 encoded string
*
* ```
* @OptionTag(converter = FuzzyMatchContainer.FuzzyMatchContainerConverter::class)
* var recentlySearchedFiles: DefaultListModel<FuzzyMatchContainer>? = DefaultListModel()
* ```
*
* @see FuzzierSettingsService
*/
class FuzzyMatchContainerConverter : Converter<DefaultListModel<FuzzyMatchContainer>>() {
override fun fromString(value: String) : DefaultListModel<FuzzyMatchContainer> {
// Fallback to an empty list if deserialization fails
try {
val data = Base64.getDecoder().decode(value)
val byteArrayInputStream = ByteArrayInputStream(data)
return ObjectInputStream(byteArrayInputStream).use { it.readObject() as DefaultListModel<FuzzyMatchContainer> }
} catch (_: XmlSerializationException) {
return DefaultListModel<FuzzyMatchContainer>();
} catch (_: IllegalArgumentException) {
return DefaultListModel<FuzzyMatchContainer>();
}
}

override fun toString(value: DefaultListModel<FuzzyMatchContainer>) : String {
val byteArrayOutputStream = ByteArrayOutputStream()
ObjectOutputStream(byteArrayOutputStream).use { it.writeObject(value) }
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ 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
package com.mituuz.fuzzier.entities

import com.intellij.openapi.roots.ContentIterator
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vfs.VirtualFile
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.ScoreCalculator
import com.mituuz.fuzzier.util.FuzzierUtil
import java.util.concurrent.Future
import javax.swing.DefaultListModel
Expand Down Expand Up @@ -92,7 +90,7 @@ class StringEvaluator(
true
}
}

fun evaluateFile(iterationFile: FuzzierUtil.IterationFile, listModel: DefaultListModel<FuzzyMatchContainer>,
searchString: String) {
val scoreCalculator = ScoreCalculator(searchString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ package com.mituuz.fuzzier.settings
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.util.xmlb.annotations.OptionTag
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FilenameType
import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FilenameType.FILE_PATH_ONLY
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENT_PROJECT_FILES
import javax.swing.DefaultListModel

@State(
name = "com.mituuz.fuzzier.FuzzierSettings",
Expand All @@ -39,6 +42,8 @@ class FuzzierSettingsService : PersistentStateComponent<FuzzierSettingsService.S
var modules: Map<String, String> = HashMap()
var isProject = false
var recentFilesMode: RecentFilesMode = RECENT_PROJECT_FILES
@OptionTag(converter = FuzzyMatchContainer.FuzzyMatchContainerConverter::class)
var recentlySearchedFiles: DefaultListModel<FuzzyMatchContainer>? = DefaultListModel()

var splitPosition: Int = 300
var exclusionSet: Set<String> = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*")
Expand Down Expand Up @@ -74,6 +79,7 @@ class FuzzierSettingsService : PersistentStateComponent<FuzzierSettingsService.S

enum class RecentFilesMode(val text: String) {
NONE("None"),
RECENT_PROJECT_FILES("Recent project files")
RECENT_PROJECT_FILES("Recent project files"),
RECENTLY_SEARCHED_FILES("Recently searched files")
}
}
6 changes: 5 additions & 1 deletion src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ class FuzzierUtil {
this.prioritizeShorterDirPaths = prioritizeShortedFilePaths;
}

fun removeModulePath(filePath: String): Pair<String, String> {
/**
* For each module in the project, check if the file path contains the module path.
* @return a pair of the file path (with the module path removed) and the module path
*/
fun extractModulePath(filePath: String): Pair<String, String> {
val modules = settingsState.modules
for (modulePath in modules.values) {
if (filePath.contains(modulePath)) {
Expand Down
Loading
Loading