diff --git a/build.gradle.kts b/build.gradle.kts
index b0a376dc..14bd09a8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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
@@ -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
@@ -63,10 +63,8 @@ intellijPlatform {
changeNotes = """
Version $currentVersion
- - Make popup dimensions persistent across projects
- - Popup dimensions are saved per screen bounds (location and size)
- - Improve popup location consistency (fixes right screen, left half issue)
- - Update kotlin-jvm and intellij-platform plugins to 2.1.0
+ - Add option to list recently searched files on popup open
+ - Update some dependencies
""".trimIndent()
ideaVersion {
diff --git a/changelog.md b/changelog.md
index d27b8618..431fd28b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -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)
diff --git a/src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
index 143edd1d..d615ed39 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
@@ -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
@@ -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
@@ -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()
- 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()
}
- val fuzzyMatchContainer =
- FuzzyMatchContainer.createOrderedContainer(i, filePathAndModule.first, filePathAndModule.second, file.name)
- listModel.addElement(fuzzyMatchContainer)
- i--
}
ApplicationManager.getApplication().invokeLater {
@@ -205,7 +205,7 @@ open class Fuzzier : FuzzyAction() {
}
}
}
-
+
private fun handleEmptySearchString(project: Project) {
if (fuzzierSettingsService.state.recentFilesMode != NONE) {
createInitialView(project)
@@ -216,7 +216,7 @@ open class Fuzzier : FuzzyAction() {
}
}
}
-
+
private fun getStringEvaluator(): StringEvaluator {
return StringEvaluator(
fuzzierSettingsService.state.exclusionSet,
@@ -224,9 +224,11 @@ open class Fuzzier : FuzzyAction() {
changeListManager
)
}
-
- private fun process(project: Project, stringEvaluator: StringEvaluator, searchString: String,
- listModel: DefaultListModel, task: Future<*>?) {
+
+ private fun process(
+ project: Project, stringEvaluator: StringEvaluator, searchString: String,
+ listModel: DefaultListModel, task: Future<*>?
+ ) {
val moduleManager = ModuleManager.getInstance(project)
if (fuzzierSettingsService.state.isProject) {
processProject(project, stringEvaluator, searchString, listModel, task)
@@ -234,16 +236,20 @@ open class Fuzzier : FuzzyAction() {
processModules(moduleManager, stringEvaluator, searchString, listModel, task)
}
}
-
- private fun processProject(project: Project, stringEvaluator: StringEvaluator,
- searchString: String, listModel: DefaultListModel, task: Future<*>?) {
+
+ private fun processProject(
+ project: Project, stringEvaluator: StringEvaluator,
+ searchString: String, listModel: DefaultListModel, task: Future<*>?
+ ) {
val filesToIterate = ConcurrentHashMap.newKeySet()
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, task: Future<*>?) {
+ private fun processModules(
+ moduleManager: ModuleManager, stringEvaluator: StringEvaluator,
+ searchString: String, listModel: DefaultListModel, task: Future<*>?
+ ) {
val filesToIterate = ConcurrentHashMap.newKeySet()
for (module in moduleManager.modules) {
FuzzierUtil.fileIndexToIterationFile(filesToIterate, module.rootManager.fileIndex, module.name, task)
@@ -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
@@ -288,6 +294,9 @@ open class Fuzzier : FuzzyAction() {
}
}
}
+ if (fuzzyMatchContainer != null) {
+ InitialViewHandler.addFileToRecentlySearchedFiles(fuzzyMatchContainer, fuzzierSettingsService)
+ }
popup?.cancel()
}
@@ -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)
}
}
}
@@ -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)
}
}
})
diff --git a/src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt b/src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
index 27dd4a2f..76c4ab0d 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
@@ -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
diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt
index bc8b4d26..864c47a6 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt
@@ -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
diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt
index e52f6c7e..5c65887e 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt
@@ -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)
@@ -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
@@ -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? = DefaultListModel()
+ * ```
+ *
+ * @see FuzzierSettingsService
+ */
+ class FuzzyMatchContainerConverter : Converter>() {
+ override fun fromString(value: String) : DefaultListModel {
+ // 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 }
+ } catch (_: XmlSerializationException) {
+ return DefaultListModel();
+ } catch (_: IllegalArgumentException) {
+ return DefaultListModel();
+ }
+ }
+
+ override fun toString(value: DefaultListModel) : String {
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ ObjectOutputStream(byteArrayOutputStream).use { it.writeObject(value) }
+ return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/mituuz/fuzzier/StringEvaluator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt
similarity index 97%
rename from src/main/kotlin/com/mituuz/fuzzier/StringEvaluator.kt
rename to src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt
index 26d761fd..b107e1c5 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/StringEvaluator.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt
@@ -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
@@ -92,7 +90,7 @@ class StringEvaluator(
true
}
}
-
+
fun evaluateFile(iterationFile: FuzzierUtil.IterationFile, listModel: DefaultListModel,
searchString: String) {
val scoreCalculator = ScoreCalculator(searchString)
diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt
index d6e383e3..35808061 100644
--- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt
+++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt
@@ -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",
@@ -39,6 +42,8 @@ class FuzzierSettingsService : PersistentStateComponent = HashMap()
var isProject = false
var recentFilesMode: RecentFilesMode = RECENT_PROJECT_FILES
+ @OptionTag(converter = FuzzyMatchContainer.FuzzyMatchContainerConverter::class)
+ var recentlySearchedFiles: DefaultListModel? = DefaultListModel()
var splitPosition: Int = 300
var exclusionSet: Set = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*")
@@ -74,6 +79,7 @@ class FuzzierSettingsService : PersistentStateComponent {
+ /**
+ * 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 {
val modules = settingsState.modules
for (modulePath in modules.values) {
if (filePath.contains(modulePath)) {
diff --git a/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt b/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt
new file mode 100644
index 00000000..00bf6d75
--- /dev/null
+++ b/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt
@@ -0,0 +1,115 @@
+/*
+MIT License
+
+Copyright (c) 2024 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.util
+
+import com.intellij.openapi.fileEditor.impl.EditorHistoryManager
+import com.mituuz.fuzzier.entities.FuzzyMatchContainer
+import com.mituuz.fuzzier.settings.FuzzierSettingsService
+import javax.swing.DefaultListModel
+
+class InitialViewHandler {
+ companion object {
+ fun getRecentProjectFiles(
+ fuzzierSettingsService: FuzzierSettingsService, fuzzierUtil: FuzzierUtil,
+ editorHistoryManager: EditorHistoryManager
+ ): DefaultListModel {
+ val editorHistory = editorHistoryManager.fileList
+ val listModel = DefaultListModel()
+ 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.extractModulePath(file.path)
+ // Don't add files that do not have a module path in the project
+ if (filePathAndModule.second == "") {
+ i--
+ continue
+ }
+ val fuzzyMatchContainer = FuzzyMatchContainer.createOrderedContainer(
+ i, filePathAndModule.first, filePathAndModule.second, file.name
+ )
+ listModel.addElement(fuzzyMatchContainer)
+ i--
+ }
+
+ return listModel
+ }
+
+ fun getRecentlySearchedFiles(fuzzierSettingsService: FuzzierSettingsService): DefaultListModel {
+ var listModel = fuzzierSettingsService.state.recentlySearchedFiles
+
+ if (listModel == null) {
+ listModel = DefaultListModel()
+ fuzzierSettingsService.state.recentlySearchedFiles = listModel
+ }
+
+ var i = 0
+ while (i < listModel.size) {
+ if (listModel[i] == null) {
+ listModel.remove(i)
+ } else {
+ i++
+ }
+ }
+
+ // Reverse the list to show the most recent searches first
+ var result = DefaultListModel()
+
+ var j = 0
+ while (j < listModel.size) {
+ val index = listModel.size - j - 1
+ result.addElement(listModel[index])
+ j++
+ }
+
+ return result
+ }
+
+ fun addFileToRecentlySearchedFiles(fuzzyMatchContainer: FuzzyMatchContainer, fuzzierSettingsService: FuzzierSettingsService) {
+ var listModel: DefaultListModel? = fuzzierSettingsService.state.recentlySearchedFiles
+
+ if (listModel == null) {
+ listModel = DefaultListModel()
+ fuzzierSettingsService.state.recentlySearchedFiles = listModel
+ }
+
+ var i = 0
+ while (i < listModel.size) {
+ if (listModel[i].filePath == fuzzyMatchContainer.filePath) {
+ listModel.remove(i)
+ } else {
+ i++
+ }
+ }
+
+ while (listModel.size > fuzzierSettingsService.state.fileListLimit - 1) {
+ listModel.remove(listModel.size - 1)
+ }
+
+ listModel.addElement(fuzzyMatchContainer)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt b/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt
index d3b77626..b6046a17 100644
--- a/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt
+++ b/src/test/kotlin/com/mituuz/fuzzier/TestUtil.kt
@@ -38,6 +38,7 @@ import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
import com.intellij.testFramework.runInEdtAndWait
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
+import com.mituuz.fuzzier.entities.StringEvaluator
import org.mockito.ArgumentMatchers.any
import javax.swing.DefaultListModel
import org.mockito.Mockito
diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt
index efb3ef18..c4b22239 100644
--- a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt
+++ b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt
@@ -29,6 +29,11 @@ import com.mituuz.fuzzier.settings.FuzzierConfiguration.END_STYLE_TAG
import com.mituuz.fuzzier.settings.FuzzierConfiguration.startStyleTag
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import javax.swing.DefaultListModel
class FuzzyMatchContainerTest {
@Suppress("unused")
@@ -75,4 +80,40 @@ class FuzzyMatchContainerTest {
i++
}
}
+
+ @Test
+ fun `Test serialization`() {
+ val score = FuzzyScore()
+ val container = FuzzyMatchContainer(score, "", "FuzzyMatchContainerTest.kt")
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ ObjectOutputStream(byteArrayOutputStream).use { it.writeObject(container) }
+
+ val byteArrayInputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
+ val deserialized = ObjectInputStream(byteArrayInputStream).use { it.readObject() as FuzzyMatchContainer }
+ assertEquals("", deserialized.filePath)
+ assertEquals("FuzzyMatchContainerTest.kt", deserialized.filename)
+ }
+
+ @Test
+ fun `Test default list serialization`() {
+ val list = DefaultListModel()
+ val score = FuzzyScore()
+ val container = FuzzyMatchContainer(score, "", "FuzzyMatchContainerTest.kt")
+ list.addElement(container)
+
+ val converter = FuzzyMatchContainer.FuzzyMatchContainerConverter()
+ val stringRep = converter.toString(list)
+
+ val deserialized: DefaultListModel = converter.fromString(stringRep)
+ assertEquals(1, deserialized.size)
+ assertEquals("", deserialized.get(0).filePath)
+ assertEquals("FuzzyMatchContainerTest.kt", deserialized.get(0).filename)
+ }
+
+ @Test
+ fun `Deserialization fails`() {
+ val converter = FuzzyMatchContainer.FuzzyMatchContainerConverter()
+ val deserialized: DefaultListModel = converter.fromString("This should not work")
+ assertEquals(0, deserialized.size)
+ }
}
\ No newline at end of file
diff --git a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt
index 4b45f3da..f4e53354 100644
--- a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt
+++ b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt
@@ -25,24 +25,29 @@ package com.mituuz.fuzzier.settings
import com.intellij.openapi.components.service
import com.intellij.testFramework.TestApplicationManager
+import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FilenameType.FILENAME_WITH_PATH_STYLED
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.NONE
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENT_PROJECT_FILES
import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class FuzzierSettingsConfigurableTest {
@Suppress("unused")
private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance()
private val state = service().state
+ private val settingsConfigurable = FuzzierSettingsConfigurable()
- @Test
- fun `Test is modified with no changes`() {
+ @BeforeEach
+ fun setUp() {
state.exclusionSet = setOf("Hello", "There")
+
state.newTab = true
- state.recentFilesMode = NONE
+ state.recentFilesMode = RECENT_PROJECT_FILES
state.prioritizeShorterDirPaths = false
state.debouncePeriod = 140
+ state.resetWindow = false
state.fileListLimit = 200
state.filenameType = FILENAME_WITH_PATH_STYLED
@@ -57,36 +62,135 @@ class FuzzierSettingsConfigurableTest {
state.matchWeightSingleChar = 6
state.matchWeightStreakModifier = 20
state.matchWeightFilename = 15
+ }
- val settingsConfigurable = FuzzierSettingsConfigurable()
- settingsConfigurable.createComponent()
+ @Test
+ fun `Configurable is instanced with no changes`() {
+ pre()
assertFalse(settingsConfigurable.isModified())
}
@Test
- fun `Test is modified with a single change`() {
- state.exclusionSet = setOf("Hello", "There")
- state.newTab = true
- state.recentFilesMode = RECENT_PROJECT_FILES
- state.debouncePeriod = 140
- state.fileListLimit = 200
+ fun exclusionSet() {
+ pre()
+ state.exclusionSet = setOf("Hello", "There", "World")
+ assertTrue(settingsConfigurable.isModified())
+ }
- state.filenameType = FILENAME_WITH_PATH_STYLED
- state.highlightFilename = false
- state.fileListFontSize = 15
- state.previewFontSize = 0
- state.fileListSpacing = 2
+ @Test
+ fun newTab() {
+ pre()
+ state.newTab = false
+ assertTrue(settingsConfigurable.isModified())
+ }
- state.tolerance = 4
- state.multiMatch = true
- state.matchWeightPartialPath = 8
- state.matchWeightSingleChar = 6
- state.matchWeightStreakModifier = 20
- state.matchWeightFilename = 15
+ @Test
+ fun recentFilesMode() {
+ pre()
+ state.recentFilesMode = NONE
+ assertTrue(settingsConfigurable.isModified())
+ }
- val settingsConfigurable = FuzzierSettingsConfigurable()
- settingsConfigurable.createComponent()
+ @Test
+ fun prioritizeShorterDirPaths() {
+ pre()
+ state.prioritizeShorterDirPaths = true
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun debouncePeriod() {
+ pre()
+ state.debouncePeriod = 150
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun fileListLimit() {
+ pre()
+ state.fileListLimit = 250
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun filenameType() {
+ pre()
+ state.filenameType = FuzzyMatchContainer.FilenameType.FILENAME_ONLY
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun highlightFilename() {
+ pre()
+ state.highlightFilename = true
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun fileListFontSize() {
+ pre()
state.fileListFontSize = 16
assertTrue(settingsConfigurable.isModified())
}
+
+ @Test
+ fun previewFontSize() {
+ pre()
+ state.previewFontSize = 14
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun fileListSpacing() {
+ pre()
+ state.fileListSpacing = 3
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun tolerance() {
+ pre()
+ state.tolerance = 5
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun multiMatch() {
+ pre()
+ state.multiMatch = false
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun matchWeightPartialPath() {
+ pre()
+ state.matchWeightPartialPath = 9
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun matchWeightSingleChar() {
+ pre()
+ state.matchWeightSingleChar = 7
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun matchWeightStreakModifier() {
+ pre()
+ state.matchWeightStreakModifier = 21
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ @Test
+ fun matchWeightFilename() {
+ pre()
+ state.matchWeightFilename = 16
+ assertTrue(settingsConfigurable.isModified())
+ }
+
+ private fun pre() {
+ settingsConfigurable.createComponent()
+ assertFalse(settingsConfigurable.isModified())
+ }
}
\ No newline at end of file
diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt
index f32686b9..199cdf54 100644
--- a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt
+++ b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt
@@ -97,18 +97,18 @@ class FuzzierUtilTest {
assertEquals(3, modules.size)
var file = myFixture.findFileInTempDir("/src1/file1")
- assertEquals("/src1/file1", fuzzierUtil.removeModulePath(file.path).first)
- var finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src1/file1", fuzzierUtil.extractModulePath(file.path).first)
+ var finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("unitTest"))
file = myFixture.findFileInTempDir("/src1/module1/file1")
- assertEquals("/src1/module1/file1", fuzzierUtil.removeModulePath(file.path).first)
- finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src1/module1/file1", fuzzierUtil.extractModulePath(file.path).first)
+ finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("unitTest"))
file = myFixture.findFileInTempDir("/src2/file1")
- assertEquals("/src2/file1", fuzzierUtil.removeModulePath(file.path).first)
- finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src2/file1", fuzzierUtil.extractModulePath(file.path).first)
+ finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("unitTest"))
}
@@ -121,18 +121,18 @@ class FuzzierUtilTest {
assertEquals(3, modules.size)
var file = myFixture.findFileInTempDir("/path/src1/file1")
- assertEquals("/src1/file1", fuzzierUtil.removeModulePath(file.path).first)
- var finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src1/file1", fuzzierUtil.extractModulePath(file.path).first)
+ var finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("path"))
file = myFixture.findFileInTempDir("/to/src2/file2")
- assertEquals("/src2/file2", fuzzierUtil.removeModulePath(file.path).first)
- finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src2/file2", fuzzierUtil.extractModulePath(file.path).first)
+ finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("to"))
file = myFixture.findFileInTempDir("/module/src3/file3")
- assertEquals("/src3/file3", fuzzierUtil.removeModulePath(file.path).first)
- finalPath = fuzzierUtil.removeModulePath(file.path).second.substringAfterLast("/");
+ assertEquals("/src3/file3", fuzzierUtil.extractModulePath(file.path).first)
+ finalPath = fuzzierUtil.extractModulePath(file.path).second.substringAfterLast("/");
assertTrue(finalPath.startsWith("module"))
}
@@ -145,7 +145,7 @@ class FuzzierUtilTest {
assertEquals(1, modules.size)
val file = myFixture.findFileInTempDir("/path/src1/file1")
- assertEquals("/file1", fuzzierUtil.removeModulePath(file.path).first)
+ assertEquals("/file1", fuzzierUtil.extractModulePath(file.path).first)
}
@Test
@@ -156,7 +156,7 @@ class FuzzierUtilTest {
val modules = service().state.modules
assertEquals(1, modules.size)
- assertEquals(Pair("/no/such/file", ""), fuzzierUtil.removeModulePath("/no/such/file"))
+ assertEquals(Pair("/no/such/file", ""), fuzzierUtil.extractModulePath("/no/such/file"))
}
@Test
diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt
new file mode 100644
index 00000000..962365a7
--- /dev/null
+++ b/src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt
@@ -0,0 +1,201 @@
+/*
+MIT License
+
+Copyright (c) 2024 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.util
+
+import com.intellij.openapi.components.service
+import com.intellij.openapi.fileEditor.impl.EditorHistoryManager
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.testFramework.TestApplicationManager
+import com.mituuz.fuzzier.entities.FuzzyMatchContainer
+import com.mituuz.fuzzier.settings.FuzzierSettingsService
+import com.mituuz.fuzzier.settings.FuzzierSettingsService.State
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+import javax.swing.DefaultListModel
+
+class InitialViewHandlerTest {
+ private lateinit var project: Project
+ private lateinit var fuzzierSettingsService: FuzzierSettingsService
+ private lateinit var fuzzierUtil: FuzzierUtil
+ private lateinit var initialViewHandler: InitialViewHandler
+ private lateinit var state: State
+ private lateinit var editorHistoryManager: EditorHistoryManager
+ @Suppress("unused") // Required for add to recently used files (fuzzierSettingsServiceInstance)
+ private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance()
+
+ @BeforeEach
+ fun setUp() {
+ project = mock(Project::class.java)
+ fuzzierSettingsService = mock(FuzzierSettingsService::class.java)
+ state = mock(State::class.java)
+ fuzzierUtil = mock(FuzzierUtil::class.java)
+ initialViewHandler = InitialViewHandler()
+ editorHistoryManager = mock(EditorHistoryManager::class.java)
+ `when`(fuzzierSettingsService.state).thenReturn(state)
+ }
+
+ @Test
+ fun `Recent project files - Verify that list is truncated when it goes over the file limit`() {
+ val virtualFile1 = mock(VirtualFile::class.java)
+ val virtualFile2 = mock(VirtualFile::class.java)
+ val fileList = listOf(
+ virtualFile1,
+ virtualFile2
+ )
+ `when`(editorHistoryManager.fileList).thenReturn(fileList)
+ `when`(fuzzierSettingsService.state.fileListLimit).thenReturn(1)
+ `when`(virtualFile1.path).thenReturn("path")
+ `when`(virtualFile1.name).thenReturn("filename1")
+ `when`(virtualFile2.path).thenReturn("path")
+ `when`(virtualFile2.name).thenReturn("filename2")
+ `when`(fuzzierUtil.extractModulePath(anyString())).thenReturn(Pair("path", "module"))
+
+ val result = InitialViewHandler.getRecentProjectFiles(fuzzierSettingsService, fuzzierUtil, editorHistoryManager)
+
+ assertEquals(1, result.size())
+ }
+
+ @Test
+ fun `Recent project files - Skip files that do not belong to the project`() {
+ val virtualFile1 = mock(VirtualFile::class.java)
+ val virtualFile2 = mock(VirtualFile::class.java)
+ val fileList = listOf(
+ virtualFile1,
+ virtualFile2
+ )
+ `when`(editorHistoryManager.fileList).thenReturn(fileList)
+ `when`(fuzzierSettingsService.state.fileListLimit).thenReturn(2)
+ `when`(virtualFile1.path).thenReturn("path")
+ `when`(virtualFile1.name).thenReturn("filename1")
+ `when`(virtualFile2.path).thenReturn("path")
+ `when`(virtualFile2.name).thenReturn("filename2")
+ `when`(fuzzierUtil.extractModulePath(anyString())).thenReturn(Pair("path", "module"), Pair("", ""))
+
+ val result = InitialViewHandler.getRecentProjectFiles(fuzzierSettingsService, fuzzierUtil, editorHistoryManager)
+
+ assertEquals(1, result.size())
+ }
+
+ @Test
+ fun `Recent project files - Empty list when no history`() {
+ `when`(editorHistoryManager.fileList).thenReturn(emptyList())
+ `when`(fuzzierSettingsService.state.fileListLimit).thenReturn(2)
+
+ val result = InitialViewHandler.getRecentProjectFiles(fuzzierSettingsService, fuzzierUtil, editorHistoryManager)
+
+ assertEquals(0, result.size())
+ }
+
+ @Test
+ fun `Recently searched files - Null returns an empty list`() {
+ `when`(fuzzierSettingsService.state.recentlySearchedFiles).thenReturn(null)
+ val result = InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService)
+ assertEquals(0, result.size())
+ }
+
+ @Test
+ fun `Recently searched files - Order of multiple files`() {
+ val fuzzyMatchContainer1 = mock(FuzzyMatchContainer::class.java)
+ val fuzzyMatchContainer2 = mock(FuzzyMatchContainer::class.java)
+ val listModel = DefaultListModel()
+ listModel.addElement(fuzzyMatchContainer1)
+ listModel.addElement(fuzzyMatchContainer2)
+ `when`(fuzzierSettingsService.state.recentlySearchedFiles).thenReturn(listModel)
+
+ val result = InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService)
+
+ assertEquals(fuzzyMatchContainer2, result[0])
+ assertEquals(fuzzyMatchContainer1, result[1])
+ }
+
+ @Test
+ fun `Recently searched files - Remove null elements from the list`() {
+ val fuzzyMatchContainer = mock(FuzzyMatchContainer::class.java)
+ val listModel = DefaultListModel()
+ listModel.addElement(fuzzyMatchContainer)
+ listModel.addElement(null)
+ listModel.addElement(null)
+ `when`(fuzzierSettingsService.state.recentlySearchedFiles).thenReturn(listModel)
+
+ val result = InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService)
+
+ assertEquals(1, result.size)
+ }
+
+ @Test
+ fun `Add file to recently used files - Null list should default to empty`() {
+ val fuzzierSettingsServiceInstance: FuzzierSettingsService = service()
+ val score = FuzzyMatchContainer.FuzzyScore()
+ val container = FuzzyMatchContainer(score, "", "")
+
+ fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null
+ InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance)
+ assertNotNull(fuzzierSettingsServiceInstance.state.recentlySearchedFiles)
+ assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles!!.size)
+ }
+
+ @Test
+ fun `Add file to recently used files - Too large list is truncated`() {
+ val fuzzierSettingsServiceInstance: FuzzierSettingsService = service()
+ val fileListLimit = 2
+ val score = FuzzyMatchContainer.FuzzyScore()
+ val container = FuzzyMatchContainer(score, "", "")
+
+ val largeList: DefaultListModel = DefaultListModel()
+ for (i in 0..25) {
+ largeList.addElement(FuzzyMatchContainer(score, "" + i, "" + i))
+ }
+
+ fuzzierSettingsServiceInstance.state.fileListLimit = fileListLimit
+
+ fuzzierSettingsServiceInstance.state.recentlySearchedFiles = largeList
+ InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance)
+ assertEquals(fileListLimit, fuzzierSettingsServiceInstance.state.recentlySearchedFiles!!.size)
+ }
+
+ @Test
+ fun `Add file to recently used files - Duplicate filenames are removed`() {
+ val fuzzierSettingsServiceInstance: FuzzierSettingsService = service()
+ val fileListLimit = 20
+ val score = FuzzyMatchContainer.FuzzyScore()
+ val container = FuzzyMatchContainer(score, "", "")
+
+ val largeList: DefaultListModel = DefaultListModel()
+ repeat (26) {
+ largeList.addElement(FuzzyMatchContainer(score, "", ""))
+ }
+
+ fuzzierSettingsServiceInstance.state.fileListLimit = fileListLimit
+
+ fuzzierSettingsServiceInstance.state.recentlySearchedFiles = largeList
+ InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance)
+ assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles!!.size)
+ }
+}
\ No newline at end of file