From b661df233e21e1fc44fdd8ae2d72236921859741 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 05:38:52 +0300 Subject: [PATCH 01/17] Use immutable lists where possible and remove unnecessary list models --- .../fuzzier/entities/FuzzyMatchContainer.kt | 16 ------ .../DefaultInitialListModelProvider.kt | 15 ++---- .../initialview/RecentlySearchedFilesUtil.kt | 19 +++---- .../settings/FuzzierSettingsService.kt | 53 +++++++++---------- .../entities/FuzzyMatchContainerTest.kt | 2 +- .../RecentlySearchedFilesUtilTest.kt | 4 +- 6 files changed, 44 insertions(+), 65 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt index fcad1371..c2d26457 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt @@ -137,22 +137,6 @@ class FuzzyMatchContainer( serialized.moduleBasePath = container.basePath return serialized } - - fun fromListModel(listModel: DefaultListModel): DefaultListModel { - val serializedList = DefaultListModel() - for (i in 0 until listModel.size) { - serializedList.addElement(fromFuzzyMatchContainer(listModel[i])) - } - return serializedList - } - - fun toListModel(serializedList: DefaultListModel): DefaultListModel { - val listModel = DefaultListModel() - for (i in 0 until serializedList.size) { - listModel.addElement(serializedList[i].toFuzzyMatchContainer()) - } - return listModel - } } fun toFuzzyMatchContainer(): FuzzyMatchContainer { diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt index 57ded887..2effa3e4 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -45,7 +45,7 @@ class DefaultInitialListModelProvider( } FuzzierGlobalSettingsService.RecentFilesMode.RECENTLY_SEARCHED_FILES -> { - getRecentlySearchedFiles() + getRecentlySearchedFiles(projectState) } else -> { @@ -82,16 +82,11 @@ class DefaultInitialListModelProvider( return listModel } - fun getRecentlySearchedFiles(): DefaultListModel { + fun getRecentlySearchedFiles(state: FuzzierSettingsService.State): DefaultListModel { val result = DefaultListModel() - projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - .elements() - .toList() - .filterNotNull() - .reversed() - .let { - result.addAll(it) - } + state.getRecentlySearchedFilesAsFuzzyMatchContainer() + .asReversed() + .forEach { result.addElement(it) } return result } } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index 25001289..bd392bba 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -26,34 +26,35 @@ package com.mituuz.fuzzier.search.initialview import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer +import com.mituuz.fuzzier.entities.FuzzyMatchContainer.SerializedMatchContainer.Companion.fromFuzzyMatchContainer import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService -import javax.swing.DefaultListModel fun addFileToRecentlySearchedFiles( fuzzyContainer: FuzzyContainer, projectState: FuzzierSettingsService.State, globalState: FuzzierGlobalSettingsService.State ) { - val listModel: DefaultListModel = + val fuzzyContainers: MutableList = projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() var i = 0 - while (i < listModel.size) { - if (listModel[i].filePath == fuzzyContainer.filePath) { - listModel.remove(i) + while (i < fuzzyContainers.size) { + if (fuzzyContainers[i].filePath == fuzzyContainer.filePath) { + fuzzyContainers.removeAt(i) } else { i++ } } - while (listModel.size > globalState.fileListLimit - 1) { - listModel.remove(listModel.size - 1) + while (fuzzyContainers.size > globalState.fileListLimit - 1) { + fuzzyContainers.removeAt(fuzzyContainers.size - 1) } if (fuzzyContainer is FuzzyMatchContainer) { - listModel.addElement(fuzzyContainer) + fuzzyContainers.add(fuzzyContainer) + projectState.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) + fuzzyContainers.map { fromFuzzyMatchContainer(it) } } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index f5e4a4c9..3e7deb0e 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -1,26 +1,26 @@ /* -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. -*/ + * 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.settings import com.intellij.openapi.components.PersistentStateComponent @@ -29,7 +29,6 @@ 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 javax.swing.DefaultListModel @State( name = "com.mituuz.fuzzier.FuzzierSettings", @@ -41,15 +40,15 @@ class FuzzierSettingsService : PersistentStateComponent = HashMap() var isProject = false + @OptionTag(converter = FuzzyMatchContainer.SerializedMatchContainerConverter::class) - var recentlySearchedFiles: DefaultListModel? = DefaultListModel() + var recentlySearchedFiles: List? = listOf() var exclusionSet: Set = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*") var ignoredCharacters: String = "" - fun getRecentlySearchedFilesAsFuzzyMatchContainer(): DefaultListModel { - val list = recentlySearchedFiles ?: DefaultListModel() - return FuzzyMatchContainer.SerializedMatchContainer.toListModel(list) + fun getRecentlySearchedFilesAsFuzzyMatchContainer(): MutableList { + return recentlySearchedFiles?.map { it.toFuzzyMatchContainer() }?.toMutableList() ?: mutableListOf() } } diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt index ba089fb0..61c2eee5 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt @@ -118,7 +118,7 @@ class FuzzyMatchContainerTest { list.addElement(container) val converter = FuzzyMatchContainer.SerializedMatchContainerConverter() - val stringRep = converter.toString(FuzzyMatchContainer.SerializedMatchContainer.fromListModel(list)) + val stringRep = converter.toString(FuzzyMatchContainer.SerializedMatchContainer.fromList(list)) val deserialized: DefaultListModel = converter.fromString(stringRep) diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index d7ff4831..e078b757 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -79,7 +79,7 @@ class RecentlySearchedFilesUtilTest { fgss.fileListLimit = fileListLimit fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) + FuzzyMatchContainer.SerializedMatchContainer.fromList(largeList) addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, @@ -107,7 +107,7 @@ class RecentlySearchedFilesUtilTest { fgss.fileListLimit = fileListLimit fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) + FuzzyMatchContainer.SerializedMatchContainer.fromList(largeList) addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, From 6c1836155a85f78043379ee54182b93085a8afe4 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 06:22:56 +0300 Subject: [PATCH 02/17] Cleanup, refactor and general fixes --- .gitignore | 1 + .../fuzzier/entities/FuzzyMatchContainer.kt | 20 +++--- .../DefaultInitialListModelProvider.kt | 11 ++- .../initialview/RecentlySearchedFilesUtil.kt | 6 +- .../settings/FuzzierSettingsService.kt | 4 -- .../entities/FuzzyMatchContainerTest.kt | 14 ++-- .../RecentlySearchedFilesUtilTest.kt | 21 +++--- .../DefaultInitialListModelProviderTest.kt | 67 ++++++++++--------- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index c61bfd55..f7252b73 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ libs/ !**/src/main/**/build/ !**/src/test/**/build/ .aider* +.junie/ ### IntelliJ IDEA ### .intellijPlatform diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt index c2d26457..757075fd 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt @@ -30,7 +30,6 @@ import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService import java.io.* import java.util.* -import javax.swing.DefaultListModel class FuzzyMatchContainer( val score: FuzzyScore, @@ -139,8 +138,13 @@ class FuzzyMatchContainer( } } - fun toFuzzyMatchContainer(): FuzzyMatchContainer { - return FuzzyMatchContainer(score!!, filePath!!, filename!!, moduleBasePath!!, FileType.FILE) + fun toFuzzyMatchContainer(): FuzzyMatchContainer? { + val score = score ?: return null + val filePath = filePath ?: return null + val filename = filename ?: return null + val moduleBasePath = moduleBasePath ?: return null + + return FuzzyMatchContainer(score, filePath, filename, moduleBasePath, FileType.FILE) } var score: FuzzyScore? = null @@ -161,21 +165,21 @@ class FuzzyMatchContainer( * * @see FuzzierSettingsService */ - class SerializedMatchContainerConverter : Converter>() { - override fun fromString(value: String): DefaultListModel { + class SerializedMatchContainerConverter : Converter>() { + override fun fromString(value: String): List { // Fallback to an empty list if deserialization fails try { val data = Base64.getDecoder().decode(value) val byteArrayInputStream = ByteArrayInputStream(data) @Suppress("UNCHECKED_CAST") - return ObjectInputStream(byteArrayInputStream).use { it.readObject() as DefaultListModel } + return ObjectInputStream(byteArrayInputStream).use { it.readObject() as List } } catch (_: Exception) { - return DefaultListModel() + return listOf() } } - override fun toString(value: DefaultListModel): String { + override fun toString(value: List): String { val byteArrayOutputStream = ByteArrayOutputStream() ObjectOutputStream(byteArrayOutputStream).use { it.writeObject(value) } return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt index 2effa3e4..aea3cc92 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -82,11 +82,8 @@ class DefaultInitialListModelProvider( return listModel } - fun getRecentlySearchedFiles(state: FuzzierSettingsService.State): DefaultListModel { - val result = DefaultListModel() - state.getRecentlySearchedFilesAsFuzzyMatchContainer() - .asReversed() - .forEach { result.addElement(it) } - return result - } + fun getRecentlySearchedFiles(state: FuzzierSettingsService.State): DefaultListModel = + DefaultListModel().apply { + state.recentlySearchedFiles?.asReversed()?.forEach { addElement(it.toFuzzyMatchContainer()) } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index bd392bba..35a82722 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -36,7 +36,8 @@ fun addFileToRecentlySearchedFiles( globalState: FuzzierGlobalSettingsService.State ) { val fuzzyContainers: MutableList = - projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + projectState.recentlySearchedFiles?.mapNotNull { it.toFuzzyMatchContainer() }?.toMutableList() + ?: mutableListOf() var i = 0 while (i < fuzzyContainers.size) { @@ -54,7 +55,6 @@ fun addFileToRecentlySearchedFiles( if (fuzzyContainer is FuzzyMatchContainer) { fuzzyContainers.add(fuzzyContainer) - projectState.recentlySearchedFiles = - fuzzyContainers.map { fromFuzzyMatchContainer(it) } + projectState.recentlySearchedFiles = fuzzyContainers.map { fromFuzzyMatchContainer(it) } } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index 3e7deb0e..80c97166 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -46,10 +46,6 @@ class FuzzierSettingsService : PersistentStateComponent = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*") var ignoredCharacters: String = "" - - fun getRecentlySearchedFilesAsFuzzyMatchContainer(): MutableList { - return recentlySearchedFiles?.map { it.toFuzzyMatchContainer() }?.toMutableList() ?: mutableListOf() - } } private var state = State() diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt index 61c2eee5..a643d8d9 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt @@ -34,7 +34,6 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.ObjectInputStream import java.io.ObjectOutputStream -import javax.swing.DefaultListModel class FuzzyMatchContainerTest { @Suppress("unused") @@ -109,28 +108,27 @@ class FuzzyMatchContainerTest { @Test fun `Test default list serialization`() { - val list = DefaultListModel() val score = FuzzyScore() val container = FuzzyMatchContainer( score, "", "FuzzyMatchContainerTest.kt", "", FILE ) - list.addElement(container) + val list = listOf(FuzzyMatchContainer.SerializedMatchContainer.fromFuzzyMatchContainer(container)) val converter = FuzzyMatchContainer.SerializedMatchContainerConverter() - val stringRep = converter.toString(FuzzyMatchContainer.SerializedMatchContainer.fromList(list)) + val stringRep = converter.toString(list) - val deserialized: DefaultListModel = + val deserialized: List = converter.fromString(stringRep) assertEquals(1, deserialized.size) - assertEquals("", deserialized.get(0).filePath) - assertEquals("FuzzyMatchContainerTest.kt", deserialized.get(0).filename) + assertEquals("", deserialized[0].filePath) + assertEquals("FuzzyMatchContainerTest.kt", deserialized[0].filename) } @Test fun `Deserialization fails`() { val converter = FuzzyMatchContainer.SerializedMatchContainerConverter() - val deserialized: DefaultListModel = + val deserialized: List = converter.fromString("This should not work") assertEquals(0, deserialized.size) } diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index e078b757..54d3e108 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -35,7 +35,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test -import javax.swing.DefaultListModel class RecentlySearchedFilesUtilTest { @Suppress("unused") // Required for add to recently used files (fuzzierSettingsServiceInstance) @@ -59,8 +58,8 @@ class RecentlySearchedFilesUtilTest { fuzzierSettingsServiceInstance.state, fgss ) - assertNotNull(fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer()) - assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) + assertNotNull(fuzzierSettingsServiceInstance.state.recentlySearchedFiles) + assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size) } @Test @@ -71,15 +70,15 @@ class RecentlySearchedFilesUtilTest { val score = FuzzyMatchContainer.FuzzyScore() val container = FuzzyMatchContainer(score, "", "", "", FILE) - val largeList: DefaultListModel = DefaultListModel() + val largeList: MutableList = mutableListOf() for (i in 0..25) { - largeList.addElement(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) + largeList.add(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) } fgss.fileListLimit = fileListLimit fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromList(largeList) + largeList.map { FuzzyMatchContainer.SerializedMatchContainer.fromFuzzyMatchContainer(it) } addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, @@ -87,7 +86,7 @@ class RecentlySearchedFilesUtilTest { ) assertEquals( fileListLimit, - fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size + fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size ) } @@ -99,20 +98,20 @@ class RecentlySearchedFilesUtilTest { val score = FuzzyMatchContainer.FuzzyScore() val container = FuzzyMatchContainer(score, "", "", "", FILE) - val largeList: DefaultListModel = DefaultListModel() + val largeList: MutableList = mutableListOf() repeat(26) { - largeList.addElement(FuzzyMatchContainer(score, "", "", "", FILE)) + largeList.add(FuzzyMatchContainer(score, "", "", "", FILE)) } fgss.fileListLimit = fileListLimit fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromList(largeList) + largeList.map { FuzzyMatchContainer.SerializedMatchContainer.fromFuzzyMatchContainer(it) } addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, fgss ) - assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) + assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size) } } diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index dec7861e..d71c20c6 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -28,6 +28,7 @@ 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.entities.FuzzyMatchContainer.SerializedMatchContainer.Companion.fromFuzzyMatchContainer import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService @@ -40,15 +41,12 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import javax.swing.DefaultListModel class DefaultInitialListModelProviderTest { private lateinit var project: Project private lateinit var fuzzierSettingsService: FuzzierSettingsService private lateinit var fuzzierGlobalSettingsService: FuzzierGlobalSettingsService - private lateinit var fuzzierUtil: FuzzierUtil private lateinit var defaultInitialListModelProvider: DefaultInitialListModelProvider - private lateinit var state: State private lateinit var editorHistoryManager: EditorHistoryManager @Suppress("unused") // Required for add to recently used files (fuzzierSettingsServiceInstance) @@ -59,12 +57,10 @@ class DefaultInitialListModelProviderTest { project = mockk() fuzzierSettingsService = mockk() fuzzierGlobalSettingsService = mockk() - state = mockk() val globalState = FuzzierGlobalSettingsService.State() globalState.recentFilesMode = FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES - defaultInitialListModelProvider = DefaultInitialListModelProvider(project, globalState, state) + defaultInitialListModelProvider = DefaultInitialListModelProvider(project, globalState, State()) editorHistoryManager = mockk() - every { fuzzierSettingsService.state } returns state } @AfterEach @@ -90,7 +86,7 @@ class DefaultInitialListModelProviderTest { every { virtualFile2.path } returns "/project/path/file2" every { virtualFile2.name } returns "filename2" - val settingsState = FuzzierSettingsService.State() + val settingsState = State() settingsState.modules = mapOf("module" to "/project/path/") defaultInitialListModelProvider = DefaultInitialListModelProvider(project, fgss, settingsState) @@ -118,7 +114,7 @@ class DefaultInitialListModelProviderTest { every { virtualFile2.path } returns "/other/path/file2" every { virtualFile2.name } returns "filename2" - val settingsState = FuzzierSettingsService.State() + val settingsState = State() settingsState.modules = mapOf("module" to "/project/path/") defaultInitialListModelProvider = DefaultInitialListModelProvider(project, fgss, settingsState) @@ -143,31 +139,40 @@ class DefaultInitialListModelProviderTest { } @Test - fun `Recently searched files - Order of multiple files`() { - val fuzzyMatchContainer1 = mockk() - val fuzzyMatchContainer2 = mockk() - val listModel = DefaultListModel() - listModel.addElement(fuzzyMatchContainer1) - listModel.addElement(fuzzyMatchContainer2) - every { state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel - - val result = defaultInitialListModelProvider.getRecentlySearchedFiles() - - assertEquals(fuzzyMatchContainer2, result[0]) - assertEquals(fuzzyMatchContainer1, result[1]) - } + fun `getRecentlySearchedFiles returns recently searched files in reverse order`() { + val state = State() + state.recentlySearchedFiles = listOf( + fromFuzzyMatchContainer( + FuzzyMatchContainer( + FuzzyMatchContainer.FuzzyScore(), + "/old.kt", + "old.kt", + "/module", + FuzzyMatchContainer.FileType.FILE + ) + ), + fromFuzzyMatchContainer( + FuzzyMatchContainer( + FuzzyMatchContainer.FuzzyScore(), + "/new.kt", + "new.kt", + "/module", + FuzzyMatchContainer.FileType.FILE + ) + ), + ) - @Test - fun `Recently searched files - Remove null elements from the list`() { - val fuzzyMatchContainer = mockk() - val listModel = DefaultListModel() - listModel.addElement(fuzzyMatchContainer) - listModel.addElement(null) - listModel.addElement(null) - every { state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel + val model = defaultInitialListModelProvider.getRecentlySearchedFiles(state) - val result = defaultInitialListModelProvider.getRecentlySearchedFiles() + assertEquals("new.kt", model.getElementAt(0).filename) + assertEquals("old.kt", model.getElementAt(1).filename) + } - assertEquals(1, result.size) + @Test + fun `Recently searched files - No files`() { + val state = State() + state.recentlySearchedFiles = mutableListOf() + val result = defaultInitialListModelProvider.getRecentlySearchedFiles(state) + assertEquals(0, result.size()) } } \ No newline at end of file From c004555f457a1cda62153dcc9e8cd5b4d246813d Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 06:29:22 +0300 Subject: [PATCH 03/17] Fix tests --- .../com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt index a643d8d9..25d1e8aa 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt @@ -102,8 +102,8 @@ class FuzzyMatchContainerTest { val deserialized = ObjectInputStream(byteArrayInputStream).use { it.readObject() as FuzzyMatchContainer.SerializedMatchContainer } val fmc = deserialized.toFuzzyMatchContainer() - assertEquals("", fmc.filePath) - assertEquals("FuzzyMatchContainerTest.kt", fmc.filename) + assertEquals("", fmc?.filePath) + assertEquals("FuzzyMatchContainerTest.kt", fmc?.filename) } @Test From 2fcb1f52e212700996ce9451a76ba6902e724f30 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 06:31:36 +0300 Subject: [PATCH 04/17] Improve naming and add javadoc --- .../initialview/RecentlySearchedFilesUtil.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index 35a82722..ba9d9745 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -30,31 +30,39 @@ import com.mituuz.fuzzier.entities.FuzzyMatchContainer.SerializedMatchContainer. import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService +/** + * Adds a file to the list of recently searched files, maintaining the limit set by global settings. + * If the file already exists in the list, it is removed and re-added to ensure it appears as the most recent. + * + * @param incomingContainer The container holding information about the file being added to the list. + * @param projectState The state of the current project, containing the project's recently searched files. + * @param globalState The global settings state, including configuration like the file list limit. + */ fun addFileToRecentlySearchedFiles( - fuzzyContainer: FuzzyContainer, + incomingContainer: FuzzyContainer, projectState: FuzzierSettingsService.State, globalState: FuzzierGlobalSettingsService.State ) { - val fuzzyContainers: MutableList = + val recentFiles: MutableList = projectState.recentlySearchedFiles?.mapNotNull { it.toFuzzyMatchContainer() }?.toMutableList() ?: mutableListOf() var i = 0 - while (i < fuzzyContainers.size) { - if (fuzzyContainers[i].filePath == fuzzyContainer.filePath) { - fuzzyContainers.removeAt(i) + while (i < recentFiles.size) { + if (recentFiles[i].filePath == incomingContainer.filePath) { + recentFiles.removeAt(i) } else { i++ } } - while (fuzzyContainers.size > globalState.fileListLimit - 1) { - fuzzyContainers.removeAt(fuzzyContainers.size - 1) + while (recentFiles.size > globalState.fileListLimit - 1) { + recentFiles.removeAt(recentFiles.size - 1) } - if (fuzzyContainer is FuzzyMatchContainer) { - fuzzyContainers.add(fuzzyContainer) + if (incomingContainer is FuzzyMatchContainer) { + recentFiles.add(incomingContainer) - projectState.recentlySearchedFiles = fuzzyContainers.map { fromFuzzyMatchContainer(it) } + projectState.recentlySearchedFiles = recentFiles.map { fromFuzzyMatchContainer(it) } } } From aade9d214ffc95cbe73a26de78a443bc655b1717 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 06:38:02 +0300 Subject: [PATCH 05/17] Add javadoc --- .../mituuz/fuzzier/settings/FuzzierSettingsService.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index 80c97166..faebe165 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -30,6 +30,9 @@ import com.intellij.openapi.components.Storage import com.intellij.util.xmlb.annotations.OptionTag import com.mituuz.fuzzier.entities.FuzzyMatchContainer +/** + * Service responsible for managing the project-specific settings and state for the Fuzzier plugin. + */ @State( name = "com.mituuz.fuzzier.FuzzierSettings", storages = [Storage("FuzzierSettings.xml")], @@ -38,13 +41,20 @@ import com.mituuz.fuzzier.entities.FuzzyMatchContainer @Service(Service.Level.PROJECT) class FuzzierSettingsService : PersistentStateComponent { class State { + /** Map of module identifiers and base paths. */ var modules: Map = HashMap() + + /** Flag indicating if we should use ProjectFileIndex or modules for file iteration. */ var isProject = false + /** List of recently searched files. */ @OptionTag(converter = FuzzyMatchContainer.SerializedMatchContainerConverter::class) var recentlySearchedFiles: List? = listOf() + /** Set of file patterns to be excluded from searches. */ var exclusionSet: Set = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*") + + /** Characters to ignore during text matching. */ var ignoredCharacters: String = "" } From f4f112cdee65b7ed1c011b7304530eabd1f5642a Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:07:12 +0300 Subject: [PATCH 06/17] Collect file metadata when user selects a searched file --- .../fuzzier/entities/FileAccessMetadata.kt | 31 +++++++++++++ .../com/mituuz/fuzzier/search/Fuzzier.kt | 3 +- .../initialview/RecentlySearchedFilesUtil.kt | 46 +++++++++++++++---- .../settings/FuzzierGlobalSettingsService.kt | 1 + .../settings/FuzzierSettingsService.kt | 4 ++ .../RecentlySearchedFilesUtilTest.kt | 15 ++---- 6 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt new file mode 100644 index 00000000..70347233 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt @@ -0,0 +1,31 @@ +/* + * 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.entities + +data class FileAccessMetadata( + val filePath: String, + val lastAccessedAt: Long, + val accessCount: Int, +) \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index ad402980..f2ced081 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -144,7 +144,8 @@ open class Fuzzier : FilesystemAction() { addFileToRecentlySearchedFiles( selectedValue, projectState, - globalState + globalState.fileListLimit, + globalState.fileMetadataCacheSize ) } popup.cancel() diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index ba9d9745..4c7809bb 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -24,24 +24,29 @@ package com.mituuz.fuzzier.search.initialview +import com.mituuz.fuzzier.entities.FileAccessMetadata import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.SerializedMatchContainer.Companion.fromFuzzyMatchContainer -import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService /** - * Adds a file to the list of recently searched files, maintaining the limit set by global settings. - * If the file already exists in the list, it is removed and re-added to ensure it appears as the most recent. + * Adds a file to the list of recently searched files while ensuring that the list does not exceed + * the specified limit, maintains uniqueness, and updates the file metadata cache. * - * @param incomingContainer The container holding information about the file being added to the list. - * @param projectState The state of the current project, containing the project's recently searched files. - * @param globalState The global settings state, including configuration like the file list limit. + * @param incomingContainer The container representing the file to be added to the recently + * searched files list. + * @param projectState The current state of the project, which holds the recently searched + * files and related metadata. + * @param fileListLimit The maximum number of files that can be maintained in the list of + * recently searched files. + * @param fileMetadataCacheSize The maximum size allowed for the file metadata cache. */ fun addFileToRecentlySearchedFiles( incomingContainer: FuzzyContainer, projectState: FuzzierSettingsService.State, - globalState: FuzzierGlobalSettingsService.State + fileListLimit: Int, + fileMetadataCacheSize: Int, ) { val recentFiles: MutableList = projectState.recentlySearchedFiles?.mapNotNull { it.toFuzzyMatchContainer() }?.toMutableList() @@ -56,13 +61,38 @@ fun addFileToRecentlySearchedFiles( } } - while (recentFiles.size > globalState.fileListLimit - 1) { + while (recentFiles.size > fileListLimit - 1) { recentFiles.removeAt(recentFiles.size - 1) } if (incomingContainer is FuzzyMatchContainer) { recentFiles.add(incomingContainer) + projectState.recentFiles = addFileToLRUCache( + incomingContainer, projectState.recentFiles, fileMetadataCacheSize + ) projectState.recentlySearchedFiles = recentFiles.map { fromFuzzyMatchContainer(it) } } } + +fun addFileToLRUCache( + incomingContainer: FuzzyContainer, recentFiles: MutableList, maxSize: Int +): MutableList { + val existingIndex = recentFiles.indexOfFirst { it.filePath == incomingContainer.filePath } + + val existingEntry = if (existingIndex != -1) recentFiles.removeAt(existingIndex) else null + + val newEntry = FileAccessMetadata( + filePath = incomingContainer.filePath, + lastAccessedAt = System.currentTimeMillis(), + accessCount = (existingEntry?.accessCount ?: 0) + 1 + ) + + recentFiles.add(0, newEntry) + + while (recentFiles.size > maxSize) { + recentFiles.removeLast() + } + + return recentFiles +} diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt index e8fac6d5..a6dc81a3 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt @@ -61,6 +61,7 @@ class FuzzierGlobalSettingsService : PersistentStateComponent = emptySet() diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index faebe165..5e87c20a 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -28,6 +28,7 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.util.xmlb.annotations.OptionTag +import com.mituuz.fuzzier.entities.FileAccessMetadata import com.mituuz.fuzzier.entities.FuzzyMatchContainer /** @@ -51,6 +52,9 @@ class FuzzierSettingsService : PersistentStateComponent? = listOf() + /** Map of recently searched files. */ + var recentFiles: MutableList = mutableListOf() + /** Set of file patterns to be excluded from searches. */ var exclusionSet: Set = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*") diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index 54d3e108..88e40569 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -28,7 +28,6 @@ import com.intellij.openapi.components.service import com.intellij.testFramework.TestApplicationManager import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE -import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService import io.mockk.unmockkAll import org.junit.jupiter.api.AfterEach @@ -48,7 +47,6 @@ class RecentlySearchedFilesUtilTest { @Test fun `Add file to recently used files - Null list should default to empty`() { val fuzzierSettingsServiceInstance: FuzzierSettingsService = service() - val fgss = service().state val score = FuzzyMatchContainer.FuzzyScore() val container = FuzzyMatchContainer(score, "", "", "", FILE) @@ -56,7 +54,7 @@ class RecentlySearchedFilesUtilTest { addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, - fgss + 20, 100 ) assertNotNull(fuzzierSettingsServiceInstance.state.recentlySearchedFiles) assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size) @@ -65,7 +63,6 @@ class RecentlySearchedFilesUtilTest { @Test fun `Add file to recently used files - Too large list is truncated`() { val fuzzierSettingsServiceInstance: FuzzierSettingsService = service() - val fgss = service().state val fileListLimit = 2 val score = FuzzyMatchContainer.FuzzyScore() val container = FuzzyMatchContainer(score, "", "", "", FILE) @@ -75,14 +72,12 @@ class RecentlySearchedFilesUtilTest { largeList.add(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) } - fgss.fileListLimit = fileListLimit - fuzzierSettingsServiceInstance.state.recentlySearchedFiles = largeList.map { FuzzyMatchContainer.SerializedMatchContainer.fromFuzzyMatchContainer(it) } addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, - fgss + fileListLimit, 100 ) assertEquals( fileListLimit, @@ -93,8 +88,6 @@ class RecentlySearchedFilesUtilTest { @Test fun `Add file to recently used files - Duplicate filenames are removed`() { val fuzzierSettingsServiceInstance: FuzzierSettingsService = service() - val fgss = service().state - val fileListLimit = 20 val score = FuzzyMatchContainer.FuzzyScore() val container = FuzzyMatchContainer(score, "", "", "", FILE) @@ -103,14 +96,12 @@ class RecentlySearchedFilesUtilTest { largeList.add(FuzzyMatchContainer(score, "", "", "", FILE)) } - fgss.fileListLimit = fileListLimit - fuzzierSettingsServiceInstance.state.recentlySearchedFiles = largeList.map { FuzzyMatchContainer.SerializedMatchContainer.fromFuzzyMatchContainer(it) } addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, - fgss + 20, 100 ) assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size) } From f7064c66707f93037715cb28954697760a6c8694 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:14:11 +0300 Subject: [PATCH 07/17] Add tests for LRU cache --- .../RecentlySearchedFilesUtilTest.kt | 104 ++++++++++++++++-- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index 88e40569..c2e1c40b 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -26,29 +26,41 @@ package com.mituuz.fuzzier.search.initialview import com.intellij.openapi.components.service import com.intellij.testFramework.TestApplicationManager +import com.mituuz.fuzzier.entities.FileAccessMetadata import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE import com.mituuz.fuzzier.settings.FuzzierSettingsService import io.mockk.unmockkAll import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class RecentlySearchedFilesUtilTest { @Suppress("unused") // Required for add to recently used files (fuzzierSettingsServiceInstance) private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance() + private lateinit var fuzzierSettingsServiceInstance: FuzzierSettingsService + + @BeforeEach + fun setUp() { + fuzzierSettingsServiceInstance = service() + fuzzierSettingsServiceInstance.state.recentlySearchedFiles = mutableListOf() + } @AfterEach fun tearDown() { unmockkAll() } + private fun createContainer(path: String = ""): FuzzyMatchContainer { + return FuzzyMatchContainer(FuzzyMatchContainer.FuzzyScore(), path, path, path, FILE) + } + @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, "", "", "", FILE) + val container = createContainer() fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null addFileToRecentlySearchedFiles( @@ -62,14 +74,12 @@ class RecentlySearchedFilesUtilTest { @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, "", "", "", FILE) + val container = createContainer() val largeList: MutableList = mutableListOf() for (i in 0..25) { - largeList.add(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) + largeList.add(createContainer("" + i)) } fuzzierSettingsServiceInstance.state.recentlySearchedFiles = @@ -87,13 +97,11 @@ class RecentlySearchedFilesUtilTest { @Test fun `Add file to recently used files - Duplicate filenames are removed`() { - val fuzzierSettingsServiceInstance: FuzzierSettingsService = service() - val score = FuzzyMatchContainer.FuzzyScore() - val container = FuzzyMatchContainer(score, "", "", "", FILE) + val container = createContainer() val largeList: MutableList = mutableListOf() repeat(26) { - largeList.add(FuzzyMatchContainer(score, "", "", "", FILE)) + largeList.add(createContainer()) } fuzzierSettingsServiceInstance.state.recentlySearchedFiles = @@ -105,4 +113,78 @@ class RecentlySearchedFilesUtilTest { ) assertEquals(1, fuzzierSettingsServiceInstance.state.recentlySearchedFiles?.size) } + + @Test + fun `addFileToLRUCache - Add new file to empty cache`() { + val recentFiles = mutableListOf() + val container = createContainer("path1") + val result = addFileToLRUCache(container, recentFiles, 5) + + assertEquals(1, result.size) + assertEquals("path1", result[0].filePath) + assertEquals(1, result[0].accessCount) + } + + @Test + fun `addFileToLRUCache - Add new file to non-empty cache`() { + val recentFiles = mutableListOf( + FileAccessMetadata("path1", 100L, 1) + ) + val container = createContainer("path2") + val result = addFileToLRUCache(container, recentFiles, 5) + + assertEquals(2, result.size) + assertEquals("path2", result[0].filePath) + assertEquals(1, result[0].accessCount) + assertEquals("path1", result[1].filePath) + } + + @Test + fun `addFileToLRUCache - Add existing file`() { + val recentFiles = mutableListOf( + FileAccessMetadata("path1", 100L, 1), + FileAccessMetadata("path2", 200L, 1) + ) + val container = createContainer("path2") + val result = addFileToLRUCache(container, recentFiles, 5) + + assertEquals(2, result.size) + assertEquals("path2", result[0].filePath) + assertEquals(2, result[0].accessCount) + assertEquals("path1", result[1].filePath) + } + + @Test + fun `addFileToLRUCache - Exceeding max size`() { + val recentFiles = mutableListOf( + FileAccessMetadata("path2", 200L, 1), + FileAccessMetadata("path1", 100L, 1) + ) + val container = createContainer("path3") + val result = addFileToLRUCache(container, recentFiles, 2) + + assertEquals(2, result.size) + assertEquals("path3", result[0].filePath) + assertEquals("path2", result[1].filePath) + } + + @Test + fun `addFileToLRUCache - Last accessed at is updated`() { + val recentFiles = mutableListOf() + val container = createContainer("path1") + val startTime = System.currentTimeMillis() + val result = addFileToLRUCache(container, recentFiles, 5) + val endTime = System.currentTimeMillis() + + assertTrue(result[0].lastAccessedAt in startTime..endTime) + } + + @Test + fun `addFileToLRUCache - Max size 0`() { + val recentFiles = mutableListOf() + val container = createContainer("path1") + val result = addFileToLRUCache(container, recentFiles, 0) + + assertEquals(0, result.size) + } } From d5c4879fc052fa2fe030c44d0d32d93ee9fc9c3b Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:18:44 +0300 Subject: [PATCH 08/17] Remove last accessed time --- .../fuzzier/entities/FileAccessMetadata.kt | 1 - .../initialview/RecentlySearchedFilesUtil.kt | 1 - .../RecentlySearchedFilesUtilTest.kt | 24 +++++-------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt index 70347233..e35db32b 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt @@ -26,6 +26,5 @@ package com.mituuz.fuzzier.entities data class FileAccessMetadata( val filePath: String, - val lastAccessedAt: Long, val accessCount: Int, ) \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index 4c7809bb..9bf76cf7 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -84,7 +84,6 @@ fun addFileToLRUCache( val newEntry = FileAccessMetadata( filePath = incomingContainer.filePath, - lastAccessedAt = System.currentTimeMillis(), accessCount = (existingEntry?.accessCount ?: 0) + 1 ) diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index c2e1c40b..676dd922 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -32,10 +32,9 @@ import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE import com.mituuz.fuzzier.settings.FuzzierSettingsService import io.mockk.unmockkAll import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class RecentlySearchedFilesUtilTest { @@ -128,7 +127,7 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Add new file to non-empty cache`() { val recentFiles = mutableListOf( - FileAccessMetadata("path1", 100L, 1) + FileAccessMetadata("path1", 1) ) val container = createContainer("path2") val result = addFileToLRUCache(container, recentFiles, 5) @@ -142,8 +141,8 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Add existing file`() { val recentFiles = mutableListOf( - FileAccessMetadata("path1", 100L, 1), - FileAccessMetadata("path2", 200L, 1) + FileAccessMetadata("path1", 1), + FileAccessMetadata("path2", 1) ) val container = createContainer("path2") val result = addFileToLRUCache(container, recentFiles, 5) @@ -157,8 +156,8 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Exceeding max size`() { val recentFiles = mutableListOf( - FileAccessMetadata("path2", 200L, 1), - FileAccessMetadata("path1", 100L, 1) + FileAccessMetadata("path2", 1), + FileAccessMetadata("path1", 1) ) val container = createContainer("path3") val result = addFileToLRUCache(container, recentFiles, 2) @@ -168,17 +167,6 @@ class RecentlySearchedFilesUtilTest { assertEquals("path2", result[1].filePath) } - @Test - fun `addFileToLRUCache - Last accessed at is updated`() { - val recentFiles = mutableListOf() - val container = createContainer("path1") - val startTime = System.currentTimeMillis() - val result = addFileToLRUCache(container, recentFiles, 5) - val endTime = System.currentTimeMillis() - - assertTrue(result[0].lastAccessedAt in startTime..endTime) - } - @Test fun `addFileToLRUCache - Max size 0`() { val recentFiles = mutableListOf() From f0f608c1a7edff5110b4f430688f55b4bed4fa24 Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:46:31 +0300 Subject: [PATCH 09/17] Plumming for file usage --- .../actions/filesystem/FilesystemAction.kt | 8 ++++++++ .../fuzzier/components/TestBenchComponent.kt | 17 +++++++++++------ ...ileAccessMetadata.kt => FileAccessData.kt} | 7 ++++++- .../fuzzier/entities/FuzzyMatchContainer.kt | 3 ++- .../fuzzier/entities/ScoreCalculator.kt | 19 ++++++++++++++++++- .../fuzzier/entities/StringEvaluator.kt | 3 ++- .../initialview/RecentlySearchedFilesUtil.kt | 8 ++++---- .../settings/FuzzierSettingsService.kt | 4 ++-- .../RecentlySearchedFilesUtilTest.kt | 16 ++++++++-------- 9 files changed, 61 insertions(+), 24 deletions(-) rename src/main/kotlin/com/mituuz/fuzzier/entities/{FileAccessMetadata.kt => FileAccessData.kt} (91%) 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 cce75ecb..4fb1c86d 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt @@ -31,6 +31,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.actions.FuzzyAction import com.mituuz.fuzzier.entities.* import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector +import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.util.FuzzierUtil import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel @@ -64,12 +65,19 @@ abstract class FilesystemAction : FuzzyAction() { addAll(projectState.exclusionSet) addAll(globalState.globalExclusionSet) } + val fileUsageStats = getFileUsageMap(projectState) return StringEvaluator( combinedExclusions, projectState.modules, + fileUsageStats ) } + fun getFileUsageMap(state: FuzzierSettingsService.State): Map = + state.recentFiles.withIndex().associate { (recentIndex, stats) -> + stats.filePath to FileUsageStats(recentIndex, stats.accessCount) + } + /** * Processes a set of IterationFiles concurrently * @return a priority list which has been size limited and sorted diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index ac871b04..35a0b247 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -144,6 +144,11 @@ class TestBenchComponent : JPanel(), Disposable { addAll(projectState.exclusionSet) addAll(liveGlobalExclusions) } + val fileUsageMap = buildMap { + projectState.recentFiles.withIndex().associate { (recentIndex, stats) -> + stats.filePath to FileUsageStats(recentIndex, stats.accessCount) + } + } currentUpdateListContentJob?.cancel() currentUpdateListContentJob = actionScope.launch { @@ -151,7 +156,9 @@ class TestBenchComponent : JPanel(), Disposable { try { val stringEvaluator = StringEvaluator( - combinedExclusions, project.service().state.modules + combinedExclusions, + project.service().state.modules, + fileUsageMap ) val iterationEntries = withContext(Dispatchers.Default) { @@ -169,9 +176,8 @@ class TestBenchComponent : JPanel(), Disposable { ) } - val sortedList = - listModel.elements().toList() - .sortedByDescending { (it as FuzzyMatchContainer).getScore(prioritizeShorterDirPaths) } + val sortedList = listModel.elements().toList() + .sortedByDescending { (it as FuzzyMatchContainer).getScore(prioritizeShorterDirPaths) } val data: Array> = sortedList.map { arrayOf( (it as FuzzyMatchContainer).filename as Any, @@ -234,8 +240,7 @@ class TestBenchComponent : JPanel(), Disposable { val ss = FuzzierUtil.cleanSearchString(searchString, projectState.ignoredCharacters) val processedFiles = ConcurrentHashMap.newKeySet() val priorityQueue = PriorityQueue( - fileListLimit + 1, - compareBy { it.getScore(prioritizeShorterDirPaths) }) + fileListLimit + 1, compareBy { it.getScore(prioritizeShorterDirPaths) }) val queueLock = Any() var minimumScore: Int? = null diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt similarity index 91% rename from src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt rename to src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt index e35db32b..fbe52138 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessMetadata.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt @@ -24,7 +24,12 @@ package com.mituuz.fuzzier.entities -data class FileAccessMetadata( +data class FileAccessData( val filePath: String, val accessCount: Int, +) + +data class FileUsageStats( + val recentIndex: Int, + val accessCount: Int, ) \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt index 757075fd..a7232135 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt @@ -110,10 +110,11 @@ class FuzzyMatchContainer( var multiMatchScore = 0 var partialPathScore = 0 var filenameScore = 0 + var fileUsageScore = 0 val highlightCharacters: MutableSet = HashSet() fun getTotalScore(): Int { - return streakScore + multiMatchScore + partialPathScore + filenameScore + return streakScore + multiMatchScore + partialPathScore + filenameScore + fileUsageScore } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt index e88d70f0..163c7ee8 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt @@ -28,7 +28,8 @@ import org.apache.commons.lang3.StringUtils class ScoreCalculator( searchString: String, - private val config: MatchConfig + private val config: MatchConfig, + private val fileUsageStats: Map ) { private val lowerSearchString: String = searchString.lowercase() private val searchStringParts = lowerSearchString.split(" ") @@ -82,9 +83,25 @@ class ScoreCalculator( fuzzyScore.streakScore = (longestStreak * config.matchWeightStreakModifier) / 10 fuzzyScore.filenameScore = (longestFilenameStreak * config.matchWeightFilename) / 10 + val fileStats = fileUsageStats[currentFilePath] + fuzzyScore.fileUsageScore = calculateUsageBoost(fileStats) + return fuzzyScore } + private fun calculateUsageBoost(fileStats: FileUsageStats?): Int { + if (fileStats == null) { + return 0 + } + val recencyWeight = 12 + val frequencyWeight = 1 + + val recencyBoost = recencyWeight * (1 - fileStats.recentIndex / 10) + val frequencyBoost = frequencyWeight * fileStats.accessCount + + return (recencyBoost + frequencyBoost) + } + /** * Returns false if no match can be found, this stops the search */ diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt index d63fc673..34910155 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/StringEvaluator.kt @@ -32,13 +32,14 @@ import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType class StringEvaluator( private var exclusionList: Set, private var modules: Map, + private val fileUsageStats: Map ) { fun evaluateIteratorEntry( iteratorEntry: IterationEntry, searchString: String, matchConfig: MatchConfig ): FuzzyMatchContainer? { - val scoreCalculator = ScoreCalculator(searchString, matchConfig) + val scoreCalculator = ScoreCalculator(searchString, matchConfig, fileUsageStats) val moduleName = iteratorEntry.module val moduleBasePath = modules[moduleName] ?: return null diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index 9bf76cf7..41aea525 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -24,7 +24,7 @@ package com.mituuz.fuzzier.search.initialview -import com.mituuz.fuzzier.entities.FileAccessMetadata +import com.mituuz.fuzzier.entities.FileAccessData import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.SerializedMatchContainer.Companion.fromFuzzyMatchContainer @@ -76,13 +76,13 @@ fun addFileToRecentlySearchedFiles( } fun addFileToLRUCache( - incomingContainer: FuzzyContainer, recentFiles: MutableList, maxSize: Int -): MutableList { + incomingContainer: FuzzyContainer, recentFiles: MutableList, maxSize: Int +): MutableList { val existingIndex = recentFiles.indexOfFirst { it.filePath == incomingContainer.filePath } val existingEntry = if (existingIndex != -1) recentFiles.removeAt(existingIndex) else null - val newEntry = FileAccessMetadata( + val newEntry = FileAccessData( filePath = incomingContainer.filePath, accessCount = (existingEntry?.accessCount ?: 0) + 1 ) diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index 5e87c20a..414d3332 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -28,7 +28,7 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.util.xmlb.annotations.OptionTag -import com.mituuz.fuzzier.entities.FileAccessMetadata +import com.mituuz.fuzzier.entities.FileAccessData import com.mituuz.fuzzier.entities.FuzzyMatchContainer /** @@ -53,7 +53,7 @@ class FuzzierSettingsService : PersistentStateComponent? = listOf() /** Map of recently searched files. */ - var recentFiles: MutableList = mutableListOf() + var recentFiles: MutableList = mutableListOf() /** Set of file patterns to be excluded from searches. */ var exclusionSet: Set = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*") diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt index 676dd922..5a4c5e6a 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -26,7 +26,7 @@ package com.mituuz.fuzzier.search.initialview import com.intellij.openapi.components.service import com.intellij.testFramework.TestApplicationManager -import com.mituuz.fuzzier.entities.FileAccessMetadata +import com.mituuz.fuzzier.entities.FileAccessData import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE import com.mituuz.fuzzier.settings.FuzzierSettingsService @@ -115,7 +115,7 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Add new file to empty cache`() { - val recentFiles = mutableListOf() + val recentFiles = mutableListOf() val container = createContainer("path1") val result = addFileToLRUCache(container, recentFiles, 5) @@ -127,7 +127,7 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Add new file to non-empty cache`() { val recentFiles = mutableListOf( - FileAccessMetadata("path1", 1) + FileAccessData("path1", 1) ) val container = createContainer("path2") val result = addFileToLRUCache(container, recentFiles, 5) @@ -141,8 +141,8 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Add existing file`() { val recentFiles = mutableListOf( - FileAccessMetadata("path1", 1), - FileAccessMetadata("path2", 1) + FileAccessData("path1", 1), + FileAccessData("path2", 1) ) val container = createContainer("path2") val result = addFileToLRUCache(container, recentFiles, 5) @@ -156,8 +156,8 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Exceeding max size`() { val recentFiles = mutableListOf( - FileAccessMetadata("path2", 1), - FileAccessMetadata("path1", 1) + FileAccessData("path2", 1), + FileAccessData("path1", 1) ) val container = createContainer("path3") val result = addFileToLRUCache(container, recentFiles, 2) @@ -169,7 +169,7 @@ class RecentlySearchedFilesUtilTest { @Test fun `addFileToLRUCache - Max size 0`() { - val recentFiles = mutableListOf() + val recentFiles = mutableListOf() val container = createContainer("path1") val result = addFileToLRUCache(container, recentFiles, 0) From 3528c9fe090d757bf2b62e79f650f41eb437276c Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:49:05 +0300 Subject: [PATCH 10/17] Fix method calls --- .../com/mituuz/fuzzier/StringEvaluatorTest.kt | 2 +- .../fuzzier/entities/ScoreCalculatorTest.kt | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/test/kotlin/com/mituuz/fuzzier/StringEvaluatorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/StringEvaluatorTest.kt index c6786b3c..b439f031 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/StringEvaluatorTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/StringEvaluatorTest.kt @@ -38,7 +38,7 @@ class StringEvaluatorTest { private val moduleBasePath = "/m1/src" private fun evaluate(filePaths: List, exclusionList: Set): List { - val evaluator = StringEvaluator(exclusionList, mapOf(moduleName to moduleBasePath)) + val evaluator = StringEvaluator(exclusionList, mapOf(moduleName to moduleBasePath), mapOf()) return filePaths.mapNotNull { fp -> // Build absolute path under a fake module root so that removePrefix(moduleBasePath) works like in production diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt index 687a0432..2f3055b5 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test class ScoreCalculatorTest { @Test fun `Search string contained same index`() { - val sc = ScoreCalculator("test", MatchConfig()) + val sc = ScoreCalculator("test", MatchConfig(), mapOf()) sc.searchStringIndex = 0 sc.searchStringLength = 4 @@ -41,7 +41,7 @@ class ScoreCalculatorTest { @Test fun `Search string contained different index`() { - val sc = ScoreCalculator("test", MatchConfig()) + val sc = ScoreCalculator("test", MatchConfig(), mapOf()) sc.searchStringIndex = 0 sc.searchStringLength = 4 @@ -56,7 +56,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10 ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/test") assertEquals(4, fScore!!.streakScore) @@ -67,7 +67,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10 ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/te/st") assertEquals(2, fScore!!.streakScore) @@ -78,7 +78,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10 ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/te") assertNull(fScore) @@ -89,7 +89,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightSingleChar = 10, multiMatch = true ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/test") assertEquals(4, fScore!!.multiMatchScore) @@ -100,7 +100,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightSingleChar = 10, multiMatch = true ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/testtest") assertEquals(8, fScore!!.multiMatchScore) } @@ -110,7 +110,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightSingleChar = 10, multiMatch = true ) - val sc = ScoreCalculator("test test", matchConfig) + val sc = ScoreCalculator("test test", matchConfig, mapOf()) val fScore = sc.calculateScore("/testtest") assertEquals(8, fScore!!.multiMatchScore) } @@ -120,7 +120,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightPartialPath = 1 ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/test.kt") assertEquals(1, fScore!!.partialPathScore) } @@ -130,14 +130,14 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightFilename = 10 ) - val sc = ScoreCalculator("test", matchConfig) + val sc = ScoreCalculator("test", matchConfig, mapOf()) val fScore = sc.calculateScore("/test.kt") assertEquals(4, fScore!!.filenameScore) } @Test fun `Empty ss and fp`() { - val sc = ScoreCalculator("", MatchConfig()) + val sc = ScoreCalculator("", MatchConfig(), mapOf()) val fScore = sc.calculateScore("") assertEquals(0, fScore!!.getTotalScore()) @@ -149,7 +149,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kif", matchConfig) + val sc = ScoreCalculator("kif", matchConfig, mapOf()) val fScore = sc.calculateScore("/KotlinIsFun") @@ -164,7 +164,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kot", matchConfig) + val sc = ScoreCalculator("kot", matchConfig, mapOf()) val fScore = sc.calculateScore("/KotlinIsFun") @@ -176,21 +176,21 @@ class ScoreCalculatorTest { @Test fun `Too long ss`() { - val sc = ScoreCalculator("TooLongSearchString", MatchConfig()) + val sc = ScoreCalculator("TooLongSearchString", MatchConfig(), mapOf()) val fScore = sc.calculateScore("/KIF") assertNull(fScore) } @Test fun `No possible match`() { - val sc = ScoreCalculator("A", MatchConfig()) + val sc = ScoreCalculator("A", MatchConfig(), mapOf()) val fScore = sc.calculateScore("/KIF") assertNull(fScore) } @Test fun `Empty ss`() { - val sc = ScoreCalculator("", MatchConfig()) + val sc = ScoreCalculator("", MatchConfig(), mapOf()) val fScore = sc.calculateScore("/KIF") assertEquals(0, fScore!!.getTotalScore()) @@ -198,14 +198,14 @@ class ScoreCalculatorTest { @Test fun `No possible match split`() { - val sc = ScoreCalculator("A A B", MatchConfig()) + val sc = ScoreCalculator("A A B", MatchConfig(), mapOf()) val fScore = sc.calculateScore("/Kotlin/Is/Fun/kif.kt") assertNull(fScore) } @Test fun `Partial match split`() { - val sc = ScoreCalculator("A A K", MatchConfig()) + val sc = ScoreCalculator("A A K", MatchConfig(), mapOf()) val fScore = sc.calculateScore("/Kotlin/Is/Fun/kif.kt") assertNull(fScore) } @@ -215,7 +215,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("fun kotlin", matchConfig) + val sc = ScoreCalculator("fun kotlin", matchConfig, mapOf()) val fScore = sc.calculateScore("/Kotlin/Is/Fun/kif.kt") @@ -230,7 +230,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kif", matchConfig) + val sc = ScoreCalculator("kif", matchConfig, mapOf()) val fScore = sc.calculateScore("/Kotlin/Is/Fun/kif.kt") @@ -245,7 +245,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kif", matchConfig) + val sc = ScoreCalculator("kif", matchConfig, mapOf()) val fScore = sc.calculateScore("/Kiffer/Is/Fun/kiffer.kt") @@ -260,7 +260,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kif", matchConfig) + val sc = ScoreCalculator("kif", matchConfig, mapOf()) val fScore = sc.calculateScore("/Kif/Is/Fun/kif.kt") @@ -275,7 +275,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( matchWeightStreakModifier = 10, multiMatch = true, matchWeightSingleChar = 10, matchWeightFilename = 10 ) - val sc = ScoreCalculator("kif fun kotlin", matchConfig) + val sc = ScoreCalculator("kif fun kotlin", matchConfig, mapOf()) val fScore = sc.calculateScore("/Kotlin/Is/Fun/kif.kt") @@ -290,7 +290,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 5, ) - val sc = ScoreCalculator("kotlin", matchConfig) + val sc = ScoreCalculator("kotlin", matchConfig, mapOf()) assertNotNull(sc.calculateScore("/Kotlin")) } @@ -300,7 +300,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 5, ) - val sc = ScoreCalculator("korlin", matchConfig) + val sc = ScoreCalculator("korlin", matchConfig, mapOf()) assertNotNull(sc.calculateScore("/Kotlin")) } @@ -310,7 +310,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 1, ) - val sc = ScoreCalculator("korlin", matchConfig) + val sc = ScoreCalculator("korlin", matchConfig, mapOf()) assertNotNull(sc.calculateScore("/Kotlin")) } @@ -320,7 +320,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 1, ) - val sc = ScoreCalculator("korlnn", matchConfig) + val sc = ScoreCalculator("korlnn", matchConfig, mapOf()) assertNull(sc.calculateScore("/Kotlin")) } @@ -330,7 +330,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 1, ) - val sc = ScoreCalculator("korlin", matchConfig) + val sc = ScoreCalculator("korlin", matchConfig, mapOf()) assertNotNull(sc.calculateScore("/Kot/lin")) } @@ -340,7 +340,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 1, ) - val sc = ScoreCalculator("korlin", matchConfig) + val sc = ScoreCalculator("korlin", matchConfig, mapOf()) assertNull(sc.calculateScore("/Kot/sin")) } @@ -350,7 +350,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 2, ) - val sc = ScoreCalculator("korlin", matchConfig) + val sc = ScoreCalculator("korlin", matchConfig, mapOf()) assertNotNull(sc.calculateScore("/Kot/sin")) } @@ -360,7 +360,7 @@ class ScoreCalculatorTest { val matchConfig = MatchConfig( tolerance = 5, ) - val sc = ScoreCalculator("kotlin12345", matchConfig) + val sc = ScoreCalculator("kotlin12345", matchConfig, mapOf()) assertNull(sc.calculateScore("/Kotlin")) } From 064e9e809eb47c36f0e1bfc503566d7937eaf1da Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 07:54:46 +0300 Subject: [PATCH 11/17] Create tests for usage boost --- .../fuzzier/entities/ScoreCalculator.kt | 2 +- .../fuzzier/entities/ScoreCalculatorTest.kt | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt index 163c7ee8..856163ac 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt @@ -89,7 +89,7 @@ class ScoreCalculator( return fuzzyScore } - private fun calculateUsageBoost(fileStats: FileUsageStats?): Int { + internal fun calculateUsageBoost(fileStats: FileUsageStats?): Int { if (fileStats == null) { return 0 } diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt index 2f3055b5..9d7a15fc 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt @@ -364,4 +364,49 @@ class ScoreCalculatorTest { assertNull(sc.calculateScore("/Kotlin")) } + + + @Test + fun `Usage boost with null stats`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(0, sc.calculateUsageBoost(null)) + } + + @Test + fun `Usage boost with recent index 0`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(12, sc.calculateUsageBoost(FileUsageStats(0, 0))) + } + + @Test + fun `Usage boost with recent index 10`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(0, sc.calculateUsageBoost(FileUsageStats(10, 0))) + } + + @Test + fun `Usage boost with recent index 20`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(-12, sc.calculateUsageBoost(FileUsageStats(20, 0))) + } + + @Test + fun `Usage boost with access count`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(17, sc.calculateUsageBoost(FileUsageStats(0, 5))) + } + + @Test + fun `Usage boost affects total score`() { + val fileUsageStats = mapOf("/test.kt" to FileUsageStats(0, 5)) + val sc = ScoreCalculator("test", MatchConfig(), fileUsageStats) + val fScore = sc.calculateScore("/test.kt") + assertNotNull(fScore) + assertEquals(17, fScore!!.fileUsageScore) + // streakScore = (4 * 5) / 10 = 2 + // filenameScore = (4 * 20) / 10 = 8 + // partialPathScore = 10 (default weight) + // total = 17 + 2 + 8 + 10 = 37 + assertEquals(37, fScore.getTotalScore()) + } } \ No newline at end of file From f3c1352485f8e1ded2fc901a1c7c2201d8dfb1fd Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 08:02:58 +0300 Subject: [PATCH 12/17] Tune the scoring a bit --- build.gradle.kts | 21 +++++---- changelog.md | 6 +++ .../mituuz/fuzzier/entities/MatchConfig.kt | 4 +- .../fuzzier/entities/ScoreCalculator.kt | 13 +++--- .../fuzzier/entities/ScoreCalculatorTest.kt | 45 +++++++++++++++---- 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3db9a490..d4943384 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.3.0" +val currentVersion = "2.4.0" val myGroup = "com.mituuz" version = currentVersion group = myGroup @@ -41,16 +41,15 @@ intellijPlatform {

Version $currentVersion

  • - Update dependencies -
  • -
  • - Fix ETD errors -
  • -
  • - Update minimum version to 2026.1 -
  • -
  • - Add fallback for finding rg with wsl2 + Add file recency scoring +
      +
    • + LRU cache for file paths +
    • +
    • + Scoring is based on the recency of the file access and the frequency of the file access +
    • +
""".trimIndent() diff --git a/changelog.md b/changelog.md index 8b8e1c15..8f4472f7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## Version 2.4.0 + +- Add file recency scoring + - LRU cache for file paths + - Scoring is based on the recency of the file access and the frequency of the file access + ## Version 2.3.0 - Update dependencies diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt index 7699f021..ff5e36e3 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt @@ -27,8 +27,8 @@ package com.mituuz.fuzzier.entities data class MatchConfig( val tolerance: Int = 0, val multiMatch: Boolean = false, - val matchWeightSingleChar: Int = 1, - val matchWeightStreakModifier: Int = 5, + val matchWeightSingleChar: Int = 5, + val matchWeightStreakModifier: Int = 10, val matchWeightPartialPath: Int = 10, val matchWeightFilename: Int = 20, ) \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt index 856163ac..04f3c3af 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt @@ -93,13 +93,16 @@ class ScoreCalculator( if (fileStats == null) { return 0 } - val recencyWeight = 12 - val frequencyWeight = 1 - val recencyBoost = recencyWeight * (1 - fileStats.recentIndex / 10) - val frequencyBoost = frequencyWeight * fileStats.accessCount + // Recency boost: Start at 10 and decrease by 1 for every 2 positions in the recent list. + // This provides a smooth, non-negative boost for the top 20 recent files. + val recencyBoost = (10 - fileStats.recentIndex / 2).coerceAtLeast(0) - return (recencyBoost + frequencyBoost) + // Frequency boost: 1 point for every 5 accesses, capped at 10 points. + // This ensures frequent use is rewarded but doesn't overpower the search matches. + val frequencyBoost = (fileStats.accessCount / 5).coerceAtMost(10) + + return recencyBoost + frequencyBoost } /** diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt index 9d7a15fc..a020630b 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt @@ -375,25 +375,25 @@ class ScoreCalculatorTest { @Test fun `Usage boost with recent index 0`() { val sc = ScoreCalculator("", MatchConfig(), mapOf()) - assertEquals(12, sc.calculateUsageBoost(FileUsageStats(0, 0))) + assertEquals(10, sc.calculateUsageBoost(FileUsageStats(0, 0))) } @Test fun `Usage boost with recent index 10`() { val sc = ScoreCalculator("", MatchConfig(), mapOf()) - assertEquals(0, sc.calculateUsageBoost(FileUsageStats(10, 0))) + assertEquals(5, sc.calculateUsageBoost(FileUsageStats(10, 0))) } @Test fun `Usage boost with recent index 20`() { val sc = ScoreCalculator("", MatchConfig(), mapOf()) - assertEquals(-12, sc.calculateUsageBoost(FileUsageStats(20, 0))) + assertEquals(0, sc.calculateUsageBoost(FileUsageStats(20, 0))) } @Test fun `Usage boost with access count`() { val sc = ScoreCalculator("", MatchConfig(), mapOf()) - assertEquals(17, sc.calculateUsageBoost(FileUsageStats(0, 5))) + assertEquals(11, sc.calculateUsageBoost(FileUsageStats(0, 5))) } @Test @@ -402,11 +402,40 @@ class ScoreCalculatorTest { val sc = ScoreCalculator("test", MatchConfig(), fileUsageStats) val fScore = sc.calculateScore("/test.kt") assertNotNull(fScore) - assertEquals(17, fScore!!.fileUsageScore) - // streakScore = (4 * 5) / 10 = 2 + assertEquals(11, fScore!!.fileUsageScore) + // streakScore = (4 * 10) / 10 = 4 // filenameScore = (4 * 20) / 10 = 8 // partialPathScore = 10 (default weight) - // total = 17 + 2 + 8 + 10 = 37 - assertEquals(37, fScore.getTotalScore()) + // total = 11 + 4 + 8 + 10 = 33 + assertEquals(33, fScore.getTotalScore()) + } + + @Test + fun `Recency boost is smooth`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + assertEquals(10, sc.calculateUsageBoost(FileUsageStats(0, 0))) + assertEquals(9, sc.calculateUsageBoost(FileUsageStats(2, 0))) + assertEquals(8, sc.calculateUsageBoost(FileUsageStats(4, 0))) + assertEquals(5, sc.calculateUsageBoost(FileUsageStats(10, 0))) + assertEquals(0, sc.calculateUsageBoost(FileUsageStats(20, 0))) + assertEquals(0, sc.calculateUsageBoost(FileUsageStats(30, 0))) + } + + @Test + fun `Not in recent list is not better than late in recent list`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + + val notInListBoost = sc.calculateUsageBoost(null) // 0 + val lateInListBoost = sc.calculateUsageBoost(FileUsageStats(20, 0)) // 0 + + assertEquals(notInListBoost, lateInListBoost, "File NOT in list should be equal to a file that is very old in the list") + } + + @Test + fun `Frequency boost is capped`() { + val sc = ScoreCalculator("", MatchConfig(), mapOf()) + // 100 accesses gives 10 points (capped) + // index 10 gives 5 points + assertEquals(15, sc.calculateUsageBoost(FileUsageStats(10, 100))) } } \ No newline at end of file From f044db5f34c3cdd483599a65adc0e3388d14fafd Mon Sep 17 00:00:00 2001 From: Mitja Date: Wed, 27 May 2026 20:36:49 +0300 Subject: [PATCH 13/17] Add settings for recency weights --- build.gradle.kts | 2 +- .../actions/filesystem/FilesystemAction.kt | 4 +- .../FuzzierGlobalSettingsComponent.kt | 20 +++++ .../fuzzier/components/TestBenchComponent.kt | 8 +- .../fuzzier/entities/FuzzyMatchContainer.kt | 5 +- .../mituuz/fuzzier/entities/MatchConfig.kt | 2 + .../fuzzier/entities/ScoreCalculator.kt | 16 ++-- .../FuzzierGlobalSettingsConfigurable.kt | 6 ++ .../settings/FuzzierGlobalSettingsService.kt | 2 + .../fuzzier/entities/ScoreCalculatorTest.kt | 73 ++++++++++++++++++- .../FuzzierGlobalSettingsConfigurableTest.kt | 16 ++++ 11 files changed, 138 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d4943384..d1f49d51 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.4.0" +val currentVersion = "2.4.0-pre1" val myGroup = "com.mituuz" version = currentVersion group = myGroup 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 4fb1c86d..e29e888c 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt @@ -108,7 +108,9 @@ abstract class FilesystemAction : FuzzyAction() { globalState.matchWeightSingleChar, globalState.matchWeightStreakModifier, globalState.matchWeightPartialPath, - globalState.matchWeightFilename + globalState.matchWeightFilename, + globalState.matchWeightFrequency, + globalState.matchWeightRecency ) coroutineScope { diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierGlobalSettingsComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierGlobalSettingsComponent.kt index 593355ff..317233ff 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierGlobalSettingsComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierGlobalSettingsComponent.kt @@ -311,6 +311,24 @@ class FuzzierGlobalSettingsComponent( false ) + val matchWeightFrequency = SettingsComponent( + JBIntSpinner(10, 0, 100), "Match weight: Frequency boost", + """ + How much score should a frequency boost give.

+ Frequency boost is based on how many times a file has been accessed. + """.trimIndent(), + false + ) + + val matchWeightRecency = SettingsComponent( + JBIntSpinner(10, 0, 100), "Match weight: Recency boost", + """ + How much score should a recency boost give.

+ Recency boost is based on how recently a file has been accessed. + """.trimIndent(), + false + ) + ///////////////////////////////////////////////////////////////// // Test bench ///////////////////////////////////////////////////////////////// @@ -362,6 +380,8 @@ class FuzzierGlobalSettingsComponent( .addComponent(matchWeightPartialPath) .addComponent(matchWeightStreakModifier) .addComponent(matchWeightFilename) + .addComponent(matchWeightFrequency) + .addComponent(matchWeightRecency) .addSeparator() .addComponent(JBLabel("

Test bench

")) diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index 35a0b247..a2a166cf 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -53,7 +53,7 @@ import javax.swing.table.DefaultTableModel class TestBenchComponent : JPanel(), Disposable { private val columnNames = - arrayOf("Filename", "Filepath", "Streak", "MultiMatch", "PartialPath", "Filename", "Total") + arrayOf("Filename", "Filepath", "Streak", "MultiMatch", "PartialPath", "Filename", "Freq", "Recency", "Total") private val table = JBTable() private var searchField = EditorTextField() private var debounceJob: Job? = null @@ -186,6 +186,8 @@ class TestBenchComponent : JPanel(), Disposable { it.score.multiMatchScore as Any, it.score.partialPathScore as Any, it.score.filenameScore as Any, + it.score.frequencyScore as Any, + it.score.recencyScore as Any, it.score.getTotalScore() as Any ) }.toTypedArray() @@ -260,7 +262,9 @@ class TestBenchComponent : JPanel(), Disposable { liveSettingsComponent.matchWeightSingleChar.getIntSpinner().value as Int, liveSettingsComponent.matchWeightStreakModifier.getIntSpinner().value as Int, liveSettingsComponent.matchWeightPartialPath.getIntSpinner().value as Int, - liveSettingsComponent.matchWeightFilename.getIntSpinner().value as Int + liveSettingsComponent.matchWeightFilename.getIntSpinner().value as Int, + liveSettingsComponent.matchWeightFrequency.getIntSpinner().value as Int, + liveSettingsComponent.matchWeightRecency.getIntSpinner().value as Int ) val container = stringEvaluator.evaluateIteratorEntry(iterationFile, ss, matchConfig) diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt index a7232135..ff4255d5 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt @@ -110,11 +110,12 @@ class FuzzyMatchContainer( var multiMatchScore = 0 var partialPathScore = 0 var filenameScore = 0 - var fileUsageScore = 0 + var frequencyScore = 0 + var recencyScore = 0 val highlightCharacters: MutableSet = HashSet() fun getTotalScore(): Int { - return streakScore + multiMatchScore + partialPathScore + filenameScore + fileUsageScore + return streakScore + multiMatchScore + partialPathScore + filenameScore + frequencyScore + recencyScore } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt index ff5e36e3..a57f7426 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/MatchConfig.kt @@ -31,4 +31,6 @@ data class MatchConfig( val matchWeightStreakModifier: Int = 10, val matchWeightPartialPath: Int = 10, val matchWeightFilename: Int = 20, + val matchWeightFrequency: Int = 10, + val matchWeightRecency: Int = 10, ) \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt index 04f3c3af..2e7a853a 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt @@ -84,7 +84,7 @@ class ScoreCalculator( fuzzyScore.filenameScore = (longestFilenameStreak * config.matchWeightFilename) / 10 val fileStats = fileUsageStats[currentFilePath] - fuzzyScore.fileUsageScore = calculateUsageBoost(fileStats) + calculateUsageBoost(fileStats) return fuzzyScore } @@ -94,13 +94,13 @@ class ScoreCalculator( return 0 } - // Recency boost: Start at 10 and decrease by 1 for every 2 positions in the recent list. - // This provides a smooth, non-negative boost for the top 20 recent files. - val recencyBoost = (10 - fileStats.recentIndex / 2).coerceAtLeast(0) + val recencyBoost = ((10 - fileStats.recentIndex / 2).coerceAtLeast(0) * config.matchWeightRecency) / 10 + val frequencyBoost = ((fileStats.accessCount / 5).coerceAtMost(10) * config.matchWeightFrequency) / 10 - // Frequency boost: 1 point for every 5 accesses, capped at 10 points. - // This ensures frequent use is rewarded but doesn't overpower the search matches. - val frequencyBoost = (fileStats.accessCount / 5).coerceAtMost(10) + if (this::fuzzyScore.isInitialized) { + fuzzyScore.recencyScore = recencyBoost + fuzzyScore.frequencyScore = frequencyBoost + } return recencyBoost + frequencyBoost } @@ -168,7 +168,7 @@ class ScoreCalculator( longestFilenameStreak = 0 filePathIndex = filenameIndex - while (searchStringIndex < searchStringLength && filePathIndex < currentFilePath.length) { + while (searchStringIndex < lowerSearchString.length && filePathIndex < currentFilePath.length) { processFilenameChar(lowerSearchString[searchStringIndex]) } } diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurable.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurable.kt index 25b42249..85c5d7b9 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurable.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurable.kt @@ -84,6 +84,8 @@ class FuzzierGlobalSettingsConfigurable : Configurable { component.matchWeightSingleChar.getIntSpinner().isEnabled = state.multiMatch component.matchWeightStreakModifier.getIntSpinner().value = state.matchWeightStreakModifier component.matchWeightFilename.getIntSpinner().value = state.matchWeightFilename + component.matchWeightFrequency.getIntSpinner().value = state.matchWeightFrequency + component.matchWeightRecency.getIntSpinner().value = state.matchWeightRecency return component.jPanel } @@ -129,6 +131,8 @@ class FuzzierGlobalSettingsConfigurable : Configurable { || state.matchWeightSingleChar != component.matchWeightSingleChar.getIntSpinner().value || state.matchWeightStreakModifier != component.matchWeightStreakModifier.getIntSpinner().value || state.matchWeightFilename != component.matchWeightFilename.getIntSpinner().value + || state.matchWeightFrequency != component.matchWeightFrequency.getIntSpinner().value + || state.matchWeightRecency != component.matchWeightRecency.getIntSpinner().value || state.globalExclusionSet != newGlobalSet || state.grepBackend != component.grepBackendSelector.getGrepBackendComboBox().selectedItem } @@ -176,6 +180,8 @@ class FuzzierGlobalSettingsConfigurable : Configurable { state.matchWeightSingleChar = component.matchWeightSingleChar.getIntSpinner().value as Int state.matchWeightStreakModifier = component.matchWeightStreakModifier.getIntSpinner().value as Int state.matchWeightFilename = component.matchWeightFilename.getIntSpinner().value as Int + state.matchWeightFrequency = component.matchWeightFrequency.getIntSpinner().value as Int + state.matchWeightRecency = component.matchWeightRecency.getIntSpinner().value as Int val newGlobalSet = component.globalExclusionTextArea.text .lines() diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt index a6dc81a3..3f30251b 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsService.kt @@ -71,6 +71,8 @@ class FuzzierGlobalSettingsService : PersistentStateComponent 5 matches. + // weight 20. (5 * 20) / 10 = 10. + assertEquals(10, fScore!!.multiMatchScore) + } + + @Test + fun `All weights affect total score`() { + val fileUsageStats = mapOf("/test.kt" to FileUsageStats(0, 5)) + val matchConfig = MatchConfig( + matchWeightStreakModifier = 20, + matchWeightFilename = 30, + matchWeightPartialPath = 5, + matchWeightFrequency = 20, + matchWeightRecency = 5 + ) + val sc = ScoreCalculator("test", matchConfig, fileUsageStats) + val fScore = sc.calculateScore("/test.kt") + assertNotNull(fScore) + + // streakScore: longestStreak=4, weight=20. (4 * 20) / 10 = 8 + // filenameScore: longestFilenameStreak=4, weight=30. (4 * 30) / 10 = 12 + // partialPathScore: weight=5. 5 + // frequencyScore: access=5, weight=20. (1 * 20) / 10 = 2 + // recencyScore: index=0, weight=5. (10 * 5) / 10 = 5 + + assertEquals(8, fScore!!.streakScore) + assertEquals(12, fScore.filenameScore) + assertEquals(5, fScore.partialPathScore) + assertEquals(2, fScore.frequencyScore) + assertEquals(5, fScore.recencyScore) + assertEquals(8 + 12 + 5 + 2 + 5, fScore.getTotalScore()) // 32 + } } \ No newline at end of file diff --git a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurableTest.kt b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurableTest.kt index 39a42cf2..8110227d 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurableTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierGlobalSettingsConfigurableTest.kt @@ -62,6 +62,8 @@ class FuzzierGlobalSettingsConfigurableTest { state.matchWeightSingleChar = 6 state.matchWeightStreakModifier = 20 state.matchWeightFilename = 15 + state.matchWeightFrequency = 12 + state.matchWeightRecency = 13 } @Test @@ -270,6 +272,20 @@ class FuzzierGlobalSettingsConfigurableTest { assertTrue(settingsConfigurable.isModified) } + @Test + fun matchWeightFrequency() { + pre() + state.matchWeightFrequency = 13 + assertTrue(settingsConfigurable.isModified) + } + + @Test + fun matchWeightRecency() { + pre() + state.matchWeightRecency = 14 + assertTrue(settingsConfigurable.isModified) + } + @Test fun grepBackend_isPopulatedFromState() { state.grepBackend = FuzzierGlobalSettingsService.GrepBackend.FUZZIER From 0aa3fe7d366dd69cc7bf31d2a7ea56dcac6a13e3 Mon Sep 17 00:00:00 2001 From: Mitja Date: Mon, 1 Jun 2026 19:28:36 +0300 Subject: [PATCH 14/17] Fix incorrect casing --- build.gradle.kts | 2 +- .../fuzzier/search/initialview/RecentlySearchedFilesUtil.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d1f49d51..d4943384 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.4.0-pre1" +val currentVersion = "2.4.0" val myGroup = "com.mituuz" version = currentVersion group = myGroup diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index 41aea525..f056cf7b 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -78,7 +78,8 @@ fun addFileToRecentlySearchedFiles( fun addFileToLRUCache( incomingContainer: FuzzyContainer, recentFiles: MutableList, maxSize: Int ): MutableList { - val existingIndex = recentFiles.indexOfFirst { it.filePath == incomingContainer.filePath } + val lower = incomingContainer.filePath.lowercase() + val existingIndex = recentFiles.indexOfFirst { it.filePath.lowercase() == lower } val existingEntry = if (existingIndex != -1) recentFiles.removeAt(existingIndex) else null From 79379cf4047a87ccf57b6c3416b564e1ff1b4e68 Mon Sep 17 00:00:00 2001 From: Mitja Date: Mon, 1 Jun 2026 20:45:35 +0300 Subject: [PATCH 15/17] Allow empty constructor values and skip empty values --- .../fuzzier/actions/filesystem/FilesystemAction.kt | 2 +- .../mituuz/fuzzier/components/TestBenchComponent.kt | 10 ++++------ .../com/mituuz/fuzzier/entities/FileAccessData.kt | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) 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 e29e888c..bb95e4ae 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/filesystem/FilesystemAction.kt @@ -74,7 +74,7 @@ abstract class FilesystemAction : FuzzyAction() { } fun getFileUsageMap(state: FuzzierSettingsService.State): Map = - state.recentFiles.withIndex().associate { (recentIndex, stats) -> + state.recentFiles.filter { it.filePath.isNotBlank() }.withIndex().associate { (recentIndex, stats) -> stats.filePath to FileUsageStats(recentIndex, stats.accessCount) } diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index a2a166cf..a2a3afa9 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -144,11 +144,11 @@ class TestBenchComponent : JPanel(), Disposable { addAll(projectState.exclusionSet) addAll(liveGlobalExclusions) } - val fileUsageMap = buildMap { - projectState.recentFiles.withIndex().associate { (recentIndex, stats) -> + + val fileUsageMap = + projectState.recentFiles.filter { it.filePath.isNotBlank() }.withIndex().associate { (recentIndex, stats) -> stats.filePath to FileUsageStats(recentIndex, stats.accessCount) } - } currentUpdateListContentJob?.cancel() currentUpdateListContentJob = actionScope.launch { @@ -156,9 +156,7 @@ class TestBenchComponent : JPanel(), Disposable { try { val stringEvaluator = StringEvaluator( - combinedExclusions, - project.service().state.modules, - fileUsageMap + combinedExclusions, project.service().state.modules, fileUsageMap ) val iterationEntries = withContext(Dispatchers.Default) { diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt index fbe52138..9b4ad229 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt @@ -25,8 +25,8 @@ package com.mituuz.fuzzier.entities data class FileAccessData( - val filePath: String, - val accessCount: Int, + val filePath: String = "", + val accessCount: Int = 0, ) data class FileUsageStats( From cb442798c9575b61d1ff55961a5fb0ea249dd686 Mon Sep 17 00:00:00 2001 From: Mitja Date: Mon, 1 Jun 2026 21:06:00 +0300 Subject: [PATCH 16/17] Fix serialization and don't restrict by type --- .../mituuz/fuzzier/entities/FileAccessData.kt | 17 +++++++++++++---- .../initialview/RecentlySearchedFilesUtil.kt | 10 +++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt index 9b4ad229..77caba74 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FileAccessData.kt @@ -24,10 +24,19 @@ package com.mituuz.fuzzier.entities -data class FileAccessData( - val filePath: String = "", - val accessCount: Int = 0, -) +import java.io.Serializable + +class FileAccessData : Serializable { + var filePath: String = "" + var accessCount: Int = 0 + + constructor() + + constructor(filePath: String, accessCount: Int) { + this.filePath = filePath + this.accessCount = accessCount + } +} data class FileUsageStats( val recentIndex: Int, diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt index f056cf7b..201f612f 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -67,12 +67,12 @@ fun addFileToRecentlySearchedFiles( if (incomingContainer is FuzzyMatchContainer) { recentFiles.add(incomingContainer) - projectState.recentFiles = addFileToLRUCache( - incomingContainer, projectState.recentFiles, fileMetadataCacheSize - ) - projectState.recentlySearchedFiles = recentFiles.map { fromFuzzyMatchContainer(it) } } + + projectState.recentFiles = addFileToLRUCache( + incomingContainer, projectState.recentFiles, fileMetadataCacheSize + ) } fun addFileToLRUCache( @@ -84,7 +84,7 @@ fun addFileToLRUCache( val existingEntry = if (existingIndex != -1) recentFiles.removeAt(existingIndex) else null val newEntry = FileAccessData( - filePath = incomingContainer.filePath, + filePath = lower, accessCount = (existingEntry?.accessCount ?: 0) + 1 ) From 0b454495bd50f3664cb0f04d8918cf55db0d6b4e Mon Sep 17 00:00:00 2001 From: Mitja Date: Mon, 1 Jun 2026 21:07:57 +0300 Subject: [PATCH 17/17] Fix typo --- .../com/mituuz/fuzzier/settings/FuzzierSettingsService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index 414d3332..bb44de84 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -52,7 +52,7 @@ class FuzzierSettingsService : PersistentStateComponent? = listOf() - /** Map of recently searched files. */ + /** List of recently searched files. */ var recentFiles: MutableList = mutableListOf() /** Set of file patterns to be excluded from searches. */