From cff13f47c1c1a170ad2c27b29bbdd088b443d84d Mon Sep 17 00:00:00 2001 From: Mitja Date: Mon, 9 Feb 2026 20:02:02 +0200 Subject: [PATCH 01/10] Initial working version for initial view --- .../com/mituuz/fuzzier/actions/FuzzyAction.kt | 4 +- .../fuzzier/components/TestBenchComponent.kt | 3 +- .../fuzzier/grep/backend/FuzzierGrep.kt | 5 +- .../com/mituuz/fuzzier/search/Fuzzier.kt | 40 ++--- .../mituuz/fuzzier/search/FuzzierOpenTabs.kt | 6 + .../DefaultInitialListModelProvider.kt | 141 ++++++++++++++++++ .../initialview/InitialListModelProvider.kt | 33 ++++ .../OpenTabsInitialListModelProvider.kt | 60 ++++++++ .../com/mituuz/fuzzier/util/FuzzierUtil.kt | 122 +++++++-------- .../mituuz/fuzzier/util/InitialViewHandler.kt | 114 -------------- ...=> DefaultInitialListModelProviderTest.kt} | 38 +++-- 11 files changed, 348 insertions(+), 218 deletions(-) create mode 100644 src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt create mode 100644 src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt create mode 100644 src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt delete mode 100644 src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt rename src/test/kotlin/com/mituuz/fuzzier/util/{InitialViewHandlerTest.kt => DefaultInitialListModelProviderTest.kt} (85%) diff --git a/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt b/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt index 0de63db7..cb3e2c1e 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/actions/FuzzyAction.kt @@ -73,8 +73,6 @@ abstract class FuzzyAction : AnAction() { protected val globalState = service().state protected var defaultDoc: Document? = null private val fileTypeManager = FileTypeManager.getInstance() - - val fuzzierUtil = FuzzierUtil() protected open var currentUpdateListContentJob: Job? = null protected open var actionScope: CoroutineScope? = null @@ -104,7 +102,7 @@ abstract class FuzzyAction : AnAction() { val project = actionEvent.project if (project != null) { projectState = project.service().state - fuzzierUtil.parseModules(project) + FuzzierUtil.parseModules(project) setCustomHandlers() actionScope?.cancel() actionScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt index 6417e43f..09f63a26 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/TestBenchComponent.kt @@ -69,8 +69,7 @@ class TestBenchComponent : JPanel(), Disposable { val project = ProjectManager.getInstance().openProjects[0] projectState = project.service().state - val fuzzierUtil = FuzzierUtil() - fuzzierUtil.parseModules(project) + FuzzierUtil.parseModules(project) liveSettingsComponent = settingsComponent layout = GridLayoutManager(2, 1) diff --git a/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt b/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt index 332d3fe6..3dde7e8c 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt @@ -35,7 +35,6 @@ import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.PsiSearchHelper -import com.intellij.openapi.components.service import com.intellij.util.Processor import com.mituuz.fuzzier.entities.CaseMode import com.mituuz.fuzzier.entities.FuzzyContainer @@ -50,7 +49,6 @@ import javax.swing.DefaultListModel object FuzzierGrep : BackendStrategy { override val name = "fuzzier" - private val fuzzierUtil = FuzzierUtil() private val searchMatcher = SearchMatcher() override suspend fun handleSearch( @@ -83,7 +81,8 @@ object FuzzierGrep : BackendStrategy { val found = searchMatcher.matchesLine(line, searchString, grepConfig.caseMode) if (found) { - val (filePath, basePath) = fuzzierUtil.extractModulePath(file.path, project) + val (filePath, basePath) = + FuzzierUtil.extractModulePath(file.path, project) fileMatches.add( RowContainer( filePath, diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index d79d2e90..95cdaa64 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -28,21 +28,20 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.fileEditor.impl.EditorHistoryManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.util.SingleAlarm import com.mituuz.fuzzier.actions.filesystem.FilesystemAction import com.mituuz.fuzzier.components.FuzzyFinderComponent -import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.intellij.files.FileOpeningUtil import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector +import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider +import com.mituuz.fuzzier.search.initialview.InitialListModelProvider import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.ui.bindings.ActivationBindings import com.mituuz.fuzzier.ui.popup.PopupConfig -import com.mituuz.fuzzier.util.InitialViewHandler import javax.swing.DefaultListModel open class Fuzzier : FilesystemAction() { @@ -54,6 +53,13 @@ open class Fuzzier : FilesystemAction() { return IntelliJIterationFileCollector(projectState) } + protected open fun getInitialViewProvider(): InitialListModelProvider { + return DefaultInitialListModelProvider( + globalState, + projectState, + ) + } + override fun buildFileFilter(project: Project): (VirtualFile) -> Boolean = { vf -> !vf.isDirectory } @@ -133,7 +139,11 @@ open class Fuzzier : FilesystemAction() { globalState.newTab ) { if (selectedValue != null) { - InitialViewHandler.addFileToRecentlySearchedFiles(selectedValue, projectState, globalState) + DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + selectedValue, + projectState, + globalState + ) } popup.cancel() } @@ -144,27 +154,9 @@ open class Fuzzier : FilesystemAction() { component.fileList.setPaintBusy(true) ApplicationManager.getApplication().executeOnPooledThread { try { - val editorHistoryManager = EditorHistoryManager.getInstance(project) - - val listModel = when (globalState.recentFilesMode) { - FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> InitialViewHandler.getRecentProjectFiles( - globalState, - fuzzierUtil, - editorHistoryManager, - project - ) - - FuzzierGlobalSettingsService.RecentFilesMode.RECENTLY_SEARCHED_FILES -> InitialViewHandler.Companion.getRecentlySearchedFiles( - projectState - ) - - else -> { - DefaultListModel() - } - } - + val initialListModel = getInitialViewProvider().buildInitialView(project) ApplicationManager.getApplication().invokeLater { - component.refreshModel(listModel, getCellRenderer()) + component.refreshModel(initialListModel, getCellRenderer()) } } finally { component.fileList.setPaintBusy(false) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt index 5cc17de3..d9ebb59f 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt @@ -26,10 +26,16 @@ package com.mituuz.fuzzier.search import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.intellij.iteration.OpenTabsCollector +import com.mituuz.fuzzier.search.initialview.InitialListModelProvider +import com.mituuz.fuzzier.search.initialview.OpenTabsInitialListModelProvider class FuzzierOpenTabs : Fuzzier() { override var popupTitle: String = "Fuzzy Search (Open Tabs)" + override fun getInitialViewProvider(): InitialListModelProvider { + return OpenTabsInitialListModelProvider() + } + override fun createCollector(): IterationFileCollector { return OpenTabsCollector() } diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt new file mode 100644 index 00000000..062cee1e --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -0,0 +1,141 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +import com.intellij.openapi.fileEditor.impl.EditorHistoryManager +import com.intellij.openapi.project.Project +import com.mituuz.fuzzier.entities.FuzzyContainer +import com.mituuz.fuzzier.entities.FuzzyMatchContainer +import com.mituuz.fuzzier.entities.OrderedContainer +import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService +import com.mituuz.fuzzier.settings.FuzzierSettingsService +import com.mituuz.fuzzier.util.FuzzierUtil +import javax.swing.DefaultListModel + +class DefaultInitialListModelProvider( + val globalState: FuzzierGlobalSettingsService.State, + val projectState: FuzzierSettingsService.State, +) : InitialListModelProvider { + override fun buildInitialView(project: Project): DefaultListModel { + return when (globalState.recentFilesMode) { + FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> { + getRecentProjectFiles(project) + } + + FuzzierGlobalSettingsService.RecentFilesMode.RECENTLY_SEARCHED_FILES -> { + getRecentlySearchedFiles() + } + + else -> { + DefaultListModel() + } + } + } + + fun getRecentProjectFiles( + project: Project, + ): DefaultListModel { + val editorHistoryManager = EditorHistoryManager.getInstance(project) + val editorHistory = editorHistoryManager.fileList + val listModel = DefaultListModel() + val limit = globalState.fileListLimit + + // Start from the end of editor history (most recent file) + var i = editorHistory.size - 1 + while (i >= 0 && listModel.size() < limit) { + val file = editorHistory[i] + val filePathAndModule = FuzzierUtil.extractModulePath(file.path, project) + // Don't add files that do not have a module path in the project + if (filePathAndModule.second == "") { + i-- + continue + } + val orderedContainer = OrderedContainer( + filePathAndModule.first, filePathAndModule.second, file.name + ) + listModel.addElement(orderedContainer) + i-- + } + + return listModel + } + + fun getRecentlySearchedFiles(): DefaultListModel { + val listModel = projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + + var i = 0 + while (i < listModel.size) { + if (listModel[i] == null) { + listModel.remove(i) + } else { + i++ + } + } + + // Reverse the list to show the most recent searches first + val result = DefaultListModel() + + var j = 0 + while (j < listModel.size) { + val index = listModel.size - j - 1 + result.addElement(listModel[index]) + j++ + } + + return result + } + + companion object { + fun addFileToRecentlySearchedFiles( + fuzzyContainer: FuzzyContainer, + projectState: FuzzierSettingsService.State, + globalState: FuzzierGlobalSettingsService.State + ) { + val listModel: DefaultListModel = + projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + + var i = 0 + while (i < listModel.size) { + if (listModel[i].filePath == fuzzyContainer.filePath) { + listModel.remove(i) + } else { + i++ + } + } + + while (listModel.size > globalState.fileListLimit - 1) { + listModel.remove(listModel.size - 1) + } + + if (fuzzyContainer is FuzzyMatchContainer) { + listModel.addElement(fuzzyContainer) + projectState.recentlySearchedFiles = + FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) + } + + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt new file mode 100644 index 00000000..03b08274 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +import com.intellij.openapi.project.Project +import com.mituuz.fuzzier.entities.FuzzyContainer +import javax.swing.DefaultListModel + +interface InitialListModelProvider { + fun buildInitialView(project: Project): DefaultListModel +} \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt new file mode 100644 index 00000000..448483ea --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.mituuz.fuzzier.entities.FuzzyContainer +import com.mituuz.fuzzier.entities.OrderedContainer +import com.mituuz.fuzzier.util.FuzzierUtil +import kotlinx.html.InputType +import javax.swing.DefaultListModel + +class OpenTabsInitialListModelProvider( +) : InitialListModelProvider { + override fun buildInitialView(project: Project): DefaultListModel { + val fileEditorManager = FileEditorManager.getInstance(project) + val listModel = DefaultListModel() + + ReadAction.run { + for (vf in fileEditorManager.openFiles) { + if (!vf.isDirectory) { + val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, project) + // Don't add files that do not have a module path in the project + if (filePathAndModule.second == "") { + continue + } + val orderedContainer = OrderedContainer( + filePathAndModule.first, filePathAndModule.second, InputType.file.name + ) + listModel.add(0, orderedContainer) + } + } + } + + return listModel + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt index 1687d875..c4ea0d6b 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt @@ -49,84 +49,84 @@ class FuzzierUtil { return ret } - } - /** - * For each module in the project, check if the file path contains the module path. - * @return a pair of the file path (with the module path removed) and the module path - */ - fun extractModulePath(filePath: String, project: Project): Pair { - val modules = project.service().state.modules - for (modulePath in modules.values) { - if (filePath.contains(modulePath)) { - val file = filePath.removePrefix(modulePath) - return Pair(file, modulePath) + /** + * For each module in the project, check if the file path contains the module path. + * @return a pair of the file path (with the module path removed) and the module path + */ + fun extractModulePath(filePath: String, project: Project): Pair { + val modules = project.service().state.modules + for (modulePath in modules.values) { + if (filePath.contains(modulePath)) { + val file = filePath.removePrefix(modulePath) + return Pair(file, modulePath) + } } + return Pair(filePath, "") } - return Pair(filePath, "") - } - /** - * Parse all modules in the project and find the shortest base path for each of them. - * Combines similar module paths to the shortest possible form. - * - * Populates `FuzzierSettings.state.modules`-field - */ - fun parseModules(project: Project) { - val moduleManager = ModuleManager.getInstance(project) - - // Gather all modules and paths into a list - val moduleList: MutableList = ArrayList() - for (module in moduleManager.modules) { - val modulePath = getModulePath(module) ?: continue - moduleList.add(ModuleContainer(module.name, modulePath)) - } + /** + * Parse all modules in the project and find the shortest base path for each of them. + * Combines similar module paths to the shortest possible form. + * + * Populates `FuzzierSettings.state.modules`-field + */ + fun parseModules(project: Project) { + val moduleManager = ModuleManager.getInstance(project) + + // Gather all modules and paths into a list + val moduleList: MutableList = ArrayList() + for (module in moduleManager.modules) { + val modulePath = getModulePath(module) ?: continue + moduleList.add(ModuleContainer(module.name, modulePath)) + } - var prevModule: ModuleContainer? = null - for (currentModule in moduleList.sortedBy { it.basePath }) { - if (prevModule != null && (currentModule.basePath.startsWith(prevModule.basePath) - || prevModule.basePath.startsWith(currentModule.basePath)) - ) { - if (currentModule.basePath.length > prevModule.basePath.length) { - currentModule.basePath = prevModule.basePath - } else { - prevModule.basePath = currentModule.basePath + var prevModule: ModuleContainer? = null + for (currentModule in moduleList.sortedBy { it.basePath }) { + if (prevModule != null && (currentModule.basePath.startsWith(prevModule.basePath) + || prevModule.basePath.startsWith(currentModule.basePath)) + ) { + if (currentModule.basePath.length > prevModule.basePath.length) { + currentModule.basePath = prevModule.basePath + } else { + prevModule.basePath = currentModule.basePath + } } + + prevModule = currentModule } - prevModule = currentModule - } + if (moduleList.map { it.basePath }.distinct().size > 1) { + shortenModulePaths(moduleList) + } - if (moduleList.map { it.basePath }.distinct().size > 1) { - shortenModulePaths(moduleList) - } + if (moduleList.isEmpty() && project.basePath != null) { + moduleList.add(ModuleContainer(project.name, project.basePath!!)) + project.service().state.isProject = true + } - if (moduleList.isEmpty() && project.basePath != null) { - moduleList.add(ModuleContainer(project.name, project.basePath!!)) - project.service().state.isProject = true + val moduleMap = listToMap(moduleList) + project.service().state.modules = moduleMap } - val moduleMap = listToMap(moduleList) - project.service().state.modules = moduleMap - } + private fun shortenModulePaths(modules: List) { + for (module in modules) { + module.basePath = module.basePath.substringBeforeLast("/") + } + } - private fun shortenModulePaths(modules: List) { - for (module in modules) { - module.basePath = module.basePath.substringBeforeLast("/") + private fun getModulePath(module: Module): String? { + val contentRoots = module.rootManager.contentRoots + if (contentRoots.isEmpty()) { + return null + } + return contentRoots.firstOrNull()?.path } - } - private fun getModulePath(module: Module): String? { - val contentRoots = module.rootManager.contentRoots - if (contentRoots.isEmpty()) { - return null + private fun listToMap(modules: List): Map { + return modules.associateBy({ it.name }, { it.basePath }) } - return contentRoots.firstOrNull()?.path } data class ModuleContainer(val name: String, var basePath: String) - - private fun listToMap(modules: List): Map { - return modules.associateBy({ it.name }, { it.basePath }) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt b/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt deleted file mode 100644 index b9ece779..00000000 --- a/src/main/kotlin/com/mituuz/fuzzier/util/InitialViewHandler.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* -MIT License - -Copyright (c) 2025 Mitja Leino - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -package com.mituuz.fuzzier.util - -import com.intellij.openapi.fileEditor.impl.EditorHistoryManager -import com.intellij.openapi.project.Project -import com.mituuz.fuzzier.entities.FuzzyContainer -import com.mituuz.fuzzier.entities.FuzzyMatchContainer -import com.mituuz.fuzzier.entities.OrderedContainer -import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService -import com.mituuz.fuzzier.settings.FuzzierSettingsService -import javax.swing.DefaultListModel - -class InitialViewHandler { - companion object { - fun getRecentProjectFiles( - globalState: FuzzierGlobalSettingsService.State, fuzzierUtil: FuzzierUtil, - editorHistoryManager: EditorHistoryManager, - project: Project - ): DefaultListModel { - val editorHistory = editorHistoryManager.fileList - val listModel = DefaultListModel() - val limit = globalState.fileListLimit - - // Start from the end of editor history (most recent file) - var i = editorHistory.size - 1 - while (i >= 0 && listModel.size() < limit) { - val file = editorHistory[i] - val filePathAndModule = fuzzierUtil.extractModulePath(file.path, project) - // Don't add files that do not have a module path in the project - if (filePathAndModule.second == "") { - i-- - continue - } - val orderedContainer = OrderedContainer( - filePathAndModule.first, filePathAndModule.second, file.name - ) - listModel.addElement(orderedContainer) - i-- - } - - return listModel - } - - fun getRecentlySearchedFiles(projectState: FuzzierSettingsService.State): DefaultListModel { - var listModel = projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - - var i = 0 - while (i < listModel.size) { - if (listModel[i] == null) { - listModel.remove(i) - } else { - i++ - } - } - - // Reverse the list to show the most recent searches first - var result = DefaultListModel() - - var j = 0 - while (j < listModel.size) { - val index = listModel.size - j - 1 - result.addElement(listModel[index]) - j++ - } - - return result - } - - fun addFileToRecentlySearchedFiles(fuzzyContainer: FuzzyContainer, projectState: FuzzierSettingsService.State, - globalState: FuzzierGlobalSettingsService.State) { - var listModel: DefaultListModel = projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - - var i = 0 - while (i < listModel.size) { - if (listModel[i].filePath == fuzzyContainer.filePath) { - listModel.remove(i) - } else { - i++ - } - } - - while (listModel.size > globalState.fileListLimit - 1) { - listModel.remove(listModel.size - 1) - } - - if (fuzzyContainer is FuzzyMatchContainer) { - listModel.addElement(fuzzyContainer) - projectState.recentlySearchedFiles = FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) - } - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt similarity index 85% rename from src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt rename to src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index 5e83e23f..3d66f353 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/InitialViewHandlerTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -30,6 +30,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.TestApplicationManager import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE +import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService.State @@ -41,12 +42,12 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import javax.swing.DefaultListModel -class InitialViewHandlerTest { +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 initialViewHandler: InitialViewHandler + private lateinit var defaultInitialListModelProvider: DefaultInitialListModelProvider private lateinit var state: State private lateinit var editorHistoryManager: EditorHistoryManager @@ -60,7 +61,7 @@ class InitialViewHandlerTest { fuzzierGlobalSettingsService = mockk() state = mockk() fuzzierUtil = mockk() - initialViewHandler = InitialViewHandler() + defaultInitialListModelProvider = DefaultInitialListModelProvider(globalState.recentFilesMode) editorHistoryManager = mockk() every { fuzzierSettingsService.state } returns state } @@ -81,7 +82,8 @@ class InitialViewHandlerTest { every { virtualFile2.path } returns "path" every { virtualFile2.name } returns "filename2" every { fuzzierUtil.extractModulePath(any(), project) } returns Pair("path", "module") - val result = InitialViewHandler.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + val result = + DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) assertEquals(1, result.size()) } @@ -102,7 +104,8 @@ class InitialViewHandlerTest { every { virtualFile2.path } returns "path" every { virtualFile2.name } returns "filename2" every { fuzzierUtil.extractModulePath(any(), project) } returns Pair("path", "module") andThen Pair("", "") - val result = InitialViewHandler.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + val result = + DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) assertEquals(1, result.size()) } @@ -113,7 +116,8 @@ class InitialViewHandlerTest { every { editorHistoryManager.fileList } returns emptyList() fgss.fileListLimit = 2 - val result = InitialViewHandler.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + val result = + DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) assertEquals(0, result.size()) } @@ -127,7 +131,7 @@ class InitialViewHandlerTest { listModel.addElement(fuzzyMatchContainer2) every { fuzzierSettingsService.state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel - val result = InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService.state) + val result = DefaultInitialListModelProvider.getRecentlySearchedFiles(fuzzierSettingsService.state) assertEquals(fuzzyMatchContainer2, result[0]) assertEquals(fuzzyMatchContainer1, result[1]) @@ -142,7 +146,7 @@ class InitialViewHandlerTest { listModel.addElement(null) every { fuzzierSettingsService.state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel - val result = InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService.state) + val result = DefaultInitialListModelProvider.getRecentlySearchedFiles(fuzzierSettingsService.state) assertEquals(1, result.size) } @@ -155,7 +159,11 @@ class InitialViewHandlerTest { val container = FuzzyMatchContainer(score, "", "", "", FILE) fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null - InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance.state, fgss) + DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) assertNotNull(fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer()) assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) } @@ -177,7 +185,11 @@ class InitialViewHandlerTest { fuzzierSettingsServiceInstance.state.recentlySearchedFiles = FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance.state, fgss) + DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) assertEquals( fileListLimit, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size @@ -201,7 +213,11 @@ class InitialViewHandlerTest { fuzzierSettingsServiceInstance.state.recentlySearchedFiles = FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - InitialViewHandler.addFileToRecentlySearchedFiles(container, fuzzierSettingsServiceInstance.state, fgss) + DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) } } \ No newline at end of file From bb1c5288287384dda9a9b3876d5406b65e9b22f2 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:25:22 +0200 Subject: [PATCH 02/10] Correct tests --- .../DefaultInitialListModelProviderTest.kt | 63 +++++++++++++------ .../mituuz/fuzzier/util/FuzzierUtilTest.kt | 45 +++++++------ 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index 3d66f353..407d900f 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -36,6 +36,9 @@ import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService.State import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +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.BeforeEach @@ -60,12 +63,18 @@ class DefaultInitialListModelProviderTest { fuzzierSettingsService = mockk() fuzzierGlobalSettingsService = mockk() state = mockk() - fuzzierUtil = mockk() - defaultInitialListModelProvider = DefaultInitialListModelProvider(globalState.recentFilesMode) + val globalState = FuzzierGlobalSettingsService.State() + globalState.recentFilesMode = FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES + defaultInitialListModelProvider = DefaultInitialListModelProvider(globalState, state) editorHistoryManager = mockk() every { fuzzierSettingsService.state } returns state } + @AfterEach + fun tearDown() { + unmockkAll() + } + @Test fun `Recent project files - Verify that list is truncated when it goes over the file limit`() { val virtualFile1 = mockk() @@ -74,16 +83,24 @@ class DefaultInitialListModelProviderTest { virtualFile1, virtualFile2 ) - val fgss = service().state + mockkStatic(EditorHistoryManager::class) + every { EditorHistoryManager.getInstance(project) } returns editorHistoryManager every { editorHistoryManager.fileList } returns fileList + val fgss = defaultInitialListModelProvider.globalState fgss.fileListLimit = 1 - every { virtualFile1.path } returns "path" + every { virtualFile1.path } returns "/project/path/file1" every { virtualFile1.name } returns "filename1" - every { virtualFile2.path } returns "path" + every { virtualFile2.path } returns "/project/path/file2" every { virtualFile2.name } returns "filename2" - every { fuzzierUtil.extractModulePath(any(), project) } returns Pair("path", "module") + + val settingsState = FuzzierSettingsService.State() + settingsState.modules = mapOf("module" to "/project/path/") + mockkStatic("com.intellij.openapi.components.ServiceKt") + every { project.getService(FuzzierSettingsService::class.java) } returns fuzzierSettingsService + every { fuzzierSettingsService.state } returns settingsState + val result = - DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + defaultInitialListModelProvider.getRecentProjectFiles(project) assertEquals(1, result.size()) } @@ -96,28 +113,38 @@ class DefaultInitialListModelProviderTest { virtualFile1, virtualFile2 ) - val fgss = service().state + mockkStatic(EditorHistoryManager::class) + every { EditorHistoryManager.getInstance(project) } returns editorHistoryManager every { editorHistoryManager.fileList } returns fileList + val fgss = defaultInitialListModelProvider.globalState fgss.fileListLimit = 2 - every { virtualFile1.path } returns "path" + every { virtualFile1.path } returns "/project/path/file1" every { virtualFile1.name } returns "filename1" - every { virtualFile2.path } returns "path" + every { virtualFile2.path } returns "/other/path/file2" every { virtualFile2.name } returns "filename2" - every { fuzzierUtil.extractModulePath(any(), project) } returns Pair("path", "module") andThen Pair("", "") + + val settingsState = FuzzierSettingsService.State() + settingsState.modules = mapOf("module" to "/project/path/") + mockkStatic("com.intellij.openapi.components.ServiceKt") + every { project.getService(FuzzierSettingsService::class.java) } returns fuzzierSettingsService + every { fuzzierSettingsService.state } returns settingsState + val result = - DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + defaultInitialListModelProvider.getRecentProjectFiles(project) assertEquals(1, result.size()) } @Test fun `Recent project files - Empty list when no history`() { - val fgss = service().state + mockkStatic(EditorHistoryManager::class) + every { EditorHistoryManager.getInstance(project) } returns editorHistoryManager + val fgss = defaultInitialListModelProvider.globalState every { editorHistoryManager.fileList } returns emptyList() fgss.fileListLimit = 2 val result = - DefaultInitialListModelProvider.getRecentProjectFiles(fgss, fuzzierUtil, editorHistoryManager, project) + defaultInitialListModelProvider.getRecentProjectFiles(project) assertEquals(0, result.size()) } @@ -129,9 +156,9 @@ class DefaultInitialListModelProviderTest { val listModel = DefaultListModel() listModel.addElement(fuzzyMatchContainer1) listModel.addElement(fuzzyMatchContainer2) - every { fuzzierSettingsService.state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel + every { state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel - val result = DefaultInitialListModelProvider.getRecentlySearchedFiles(fuzzierSettingsService.state) + val result = defaultInitialListModelProvider.getRecentlySearchedFiles() assertEquals(fuzzyMatchContainer2, result[0]) assertEquals(fuzzyMatchContainer1, result[1]) @@ -144,9 +171,9 @@ class DefaultInitialListModelProviderTest { listModel.addElement(fuzzyMatchContainer) listModel.addElement(null) listModel.addElement(null) - every { fuzzierSettingsService.state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel + every { state.getRecentlySearchedFilesAsFuzzyMatchContainer() } returns listModel - val result = DefaultInitialListModelProvider.getRecentlySearchedFiles(fuzzierSettingsService.state) + val result = defaultInitialListModelProvider.getRecentlySearchedFiles() assertEquals(1, result.size) } diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt index ae48d384..9946edc1 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt @@ -36,7 +36,6 @@ import javax.swing.DefaultListModel class FuzzierUtilTest { @Suppress("unused") private val testApplicationManager = TestApplicationManager.getInstance() - private val fuzzierUtil = FuzzierUtil() private val listModel = DefaultListModel() private val testUtil = TestUtil() @@ -52,7 +51,7 @@ class FuzzierUtilTest { listOf("src2", "/src2/file2"), listOf("src3", "/src3/file3") ) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules assertEquals(3, modules.size) @@ -69,7 +68,7 @@ class FuzzierUtilTest { listOf("to/src2", "/to/src2/file2"), listOf("module/src3", "/module/src3/file3") ) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules assertEquals(3, modules.size) @@ -87,7 +86,7 @@ class FuzzierUtilTest { listOf("src1/module1", "/src1/module1/file1"), listOf("src1/module2", "/src1/module2/file1") ) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules @@ -104,24 +103,24 @@ class FuzzierUtilTest { listOf("src1/module1", "/src1/module1/file1"), listOf("src2", "/src2/file1") ) val project = myFixture.project - fuzzierUtil.parseModules(project) + FuzzierUtil.parseModules(project) val modules = myFixture.project.service().state.modules assertEquals(3, modules.size) var file = myFixture.findFileInTempDir("/src1/file1") - assertEquals("/src1/file1", fuzzierUtil.extractModulePath(file.path, project).first) - var finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, project).first) + var finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) file = myFixture.findFileInTempDir("/src1/module1/file1") - assertEquals("/src1/module1/file1", fuzzierUtil.extractModulePath(file.path, project).first) - finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/module1/file1", FuzzierUtil.extractModulePath(file.path, project).first) + finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) file = myFixture.findFileInTempDir("/src2/file1") - assertEquals("/src2/file1", fuzzierUtil.extractModulePath(file.path, project).first) - finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src2/file1", FuzzierUtil.extractModulePath(file.path, project).first) + finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) } @@ -133,54 +132,54 @@ class FuzzierUtilTest { listOf("module/src3", "/module/src3/file3") ) val project = myFixture.project - fuzzierUtil.parseModules(project) + FuzzierUtil.parseModules(project) val modules = myFixture.project.service().state.modules assertEquals(3, modules.size) var file = myFixture.findFileInTempDir("/path/src1/file1") - assertEquals("/src1/file1", fuzzierUtil.extractModulePath(file.path, project).first) - var finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, project).first) + var finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("path")) file = myFixture.findFileInTempDir("/to/src2/file2") - assertEquals("/src2/file2", fuzzierUtil.extractModulePath(file.path, project).first) - finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src2/file2", FuzzierUtil.extractModulePath(file.path, project).first) + finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("to")) file = myFixture.findFileInTempDir("/module/src3/file3") - assertEquals("/src3/file3", fuzzierUtil.extractModulePath(file.path, project).first) - finalPath = fuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src3/file3", FuzzierUtil.extractModulePath(file.path, project).first) + finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") assertTrue(finalPath.startsWith("module")) } @Test fun `Remove module paths, point only to project root`() { val myFixture = testUtil.setUpMultiModuleProject(listOf("path/src1", "/path/src1/file1")) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules assertEquals(1, modules.size) val file = myFixture.findFileInTempDir("/path/src1/file1") - assertEquals("/file1", fuzzierUtil.extractModulePath(file.path, myFixture.project).first) + assertEquals("/file1", FuzzierUtil.extractModulePath(file.path, myFixture.project).first) } @Test fun `Remove module paths, file not included`() { val myFixture = testUtil.setUpMultiModuleProject(listOf("path/src1", "/path/src1/file1")) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules assertEquals(1, modules.size) - assertEquals(Pair("/no/such/file", ""), fuzzierUtil.extractModulePath("/no/such/file", myFixture.project)) + assertEquals(Pair("/no/such/file", ""), FuzzierUtil.extractModulePath("/no/such/file", myFixture.project)) } @Test fun parseModulesSingleModule() { val myFixture = testUtil.setUpMultiModuleProject(listOf("src1", "/src1/file1")) - fuzzierUtil.parseModules(myFixture.project) + FuzzierUtil.parseModules(myFixture.project) val modules = myFixture.project.service().state.modules assertEquals(1, modules.size) From 445a745c227401054199104d653715b9a8f03767 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:26:52 +0200 Subject: [PATCH 03/10] Fix incorrect name --- .../search/initialview/OpenTabsInitialListModelProvider.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt index 448483ea..71607abd 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -30,7 +30,6 @@ import com.intellij.openapi.project.Project import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.OrderedContainer import com.mituuz.fuzzier.util.FuzzierUtil -import kotlinx.html.InputType import javax.swing.DefaultListModel class OpenTabsInitialListModelProvider( @@ -48,7 +47,7 @@ class OpenTabsInitialListModelProvider( continue } val orderedContainer = OrderedContainer( - filePathAndModule.first, filePathAndModule.second, InputType.file.name + filePathAndModule.first, filePathAndModule.second, vf.name ) listModel.add(0, orderedContainer) } From 8af8ab068245a65e158b7e27d22a10a35dc695cf Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:32:56 +0200 Subject: [PATCH 04/10] Streamline recently searched files --- .../DefaultInitialListModelProvider.kt | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) 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 062cee1e..45d19055 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -83,27 +83,15 @@ class DefaultInitialListModelProvider( } fun getRecentlySearchedFiles(): DefaultListModel { - val listModel = projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - - var i = 0 - while (i < listModel.size) { - if (listModel[i] == null) { - listModel.remove(i) - } else { - i++ - } - } - - // Reverse the list to show the most recent searches first val result = DefaultListModel() - - var j = 0 - while (j < listModel.size) { - val index = listModel.size - j - 1 - result.addElement(listModel[index]) - j++ - } - + projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + .elements() + .toList() + .filterNotNull() + .reversed() + .let { + result.addAll(it) + } return result } From c5f23efb6f33ada04897dc589e2a0654a75cf12c Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:39:22 +0200 Subject: [PATCH 05/10] Simplify file path extractor signature --- .../fuzzier/grep/backend/FuzzierGrep.kt | 3 +- .../DefaultInitialListModelProvider.kt | 2 +- .../OpenTabsInitialListModelProvider.kt | 5 +++- .../com/mituuz/fuzzier/util/FuzzierUtil.kt | 3 +- .../DefaultInitialListModelProviderTest.kt | 8 ++---- .../mituuz/fuzzier/util/FuzzierUtilTest.kt | 28 +++++++++---------- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt b/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt index 3dde7e8c..ea4154ab 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/grep/backend/FuzzierGrep.kt @@ -65,6 +65,7 @@ object FuzzierGrep : BackendStrategy { val maxResults = service().state.fileListLimit val batcher = ResultBatcher() + val modules = project.service().state.modules for (file in files) { currentCoroutineContext().ensureActive() @@ -82,7 +83,7 @@ object FuzzierGrep : BackendStrategy { if (found) { val (filePath, basePath) = - FuzzierUtil.extractModulePath(file.path, project) + FuzzierUtil.extractModulePath(file.path, modules) fileMatches.add( RowContainer( filePath, 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 45d19055..2b450357 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -66,7 +66,7 @@ class DefaultInitialListModelProvider( var i = editorHistory.size - 1 while (i >= 0 && listModel.size() < limit) { val file = editorHistory[i] - val filePathAndModule = FuzzierUtil.extractModulePath(file.path, project) + val filePathAndModule = FuzzierUtil.extractModulePath(file.path, projectState.modules) // Don't add files that do not have a module path in the project if (filePathAndModule.second == "") { i-- diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt index 71607abd..0f60c9c4 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -25,10 +25,12 @@ package com.mituuz.fuzzier.search.initialview import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.OrderedContainer +import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.util.FuzzierUtil import javax.swing.DefaultListModel @@ -37,11 +39,12 @@ class OpenTabsInitialListModelProvider( override fun buildInitialView(project: Project): DefaultListModel { val fileEditorManager = FileEditorManager.getInstance(project) val listModel = DefaultListModel() + val modules = project.service().state.modules ReadAction.run { for (vf in fileEditorManager.openFiles) { if (!vf.isDirectory) { - val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, project) + val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, modules) // Don't add files that do not have a module path in the project if (filePathAndModule.second == "") { continue diff --git a/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt index c4ea0d6b..7084f495 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt @@ -54,8 +54,7 @@ class FuzzierUtil { * For each module in the project, check if the file path contains the module path. * @return a pair of the file path (with the module path removed) and the module path */ - fun extractModulePath(filePath: String, project: Project): Pair { - val modules = project.service().state.modules + fun extractModulePath(filePath: String, modules: Map): Pair { for (modulePath in modules.values) { if (filePath.contains(modulePath)) { val file = filePath.removePrefix(modulePath) diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index 407d900f..9695bd74 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -95,9 +95,7 @@ class DefaultInitialListModelProviderTest { val settingsState = FuzzierSettingsService.State() settingsState.modules = mapOf("module" to "/project/path/") - mockkStatic("com.intellij.openapi.components.ServiceKt") - every { project.getService(FuzzierSettingsService::class.java) } returns fuzzierSettingsService - every { fuzzierSettingsService.state } returns settingsState + defaultInitialListModelProvider = DefaultInitialListModelProvider(fgss, settingsState) val result = defaultInitialListModelProvider.getRecentProjectFiles(project) @@ -125,9 +123,7 @@ class DefaultInitialListModelProviderTest { val settingsState = FuzzierSettingsService.State() settingsState.modules = mapOf("module" to "/project/path/") - mockkStatic("com.intellij.openapi.components.ServiceKt") - every { project.getService(FuzzierSettingsService::class.java) } returns fuzzierSettingsService - every { fuzzierSettingsService.state } returns settingsState + defaultInitialListModelProvider = DefaultInitialListModelProvider(fgss, settingsState) val result = defaultInitialListModelProvider.getRecentProjectFiles(project) diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt index 9946edc1..41cbef4e 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/FuzzierUtilTest.kt @@ -109,18 +109,18 @@ class FuzzierUtilTest { assertEquals(3, modules.size) var file = myFixture.findFileInTempDir("/src1/file1") - assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, project).first) - var finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, modules).first) + var finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) file = myFixture.findFileInTempDir("/src1/module1/file1") - assertEquals("/src1/module1/file1", FuzzierUtil.extractModulePath(file.path, project).first) - finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/module1/file1", FuzzierUtil.extractModulePath(file.path, modules).first) + finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) file = myFixture.findFileInTempDir("/src2/file1") - assertEquals("/src2/file1", FuzzierUtil.extractModulePath(file.path, project).first) - finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src2/file1", FuzzierUtil.extractModulePath(file.path, modules).first) + finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("unitTest")) } @@ -138,18 +138,18 @@ class FuzzierUtilTest { assertEquals(3, modules.size) var file = myFixture.findFileInTempDir("/path/src1/file1") - assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, project).first) - var finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src1/file1", FuzzierUtil.extractModulePath(file.path, modules).first) + var finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("path")) file = myFixture.findFileInTempDir("/to/src2/file2") - assertEquals("/src2/file2", FuzzierUtil.extractModulePath(file.path, project).first) - finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src2/file2", FuzzierUtil.extractModulePath(file.path, modules).first) + finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("to")) file = myFixture.findFileInTempDir("/module/src3/file3") - assertEquals("/src3/file3", FuzzierUtil.extractModulePath(file.path, project).first) - finalPath = FuzzierUtil.extractModulePath(file.path, project).second.substringAfterLast("/") + assertEquals("/src3/file3", FuzzierUtil.extractModulePath(file.path, modules).first) + finalPath = FuzzierUtil.extractModulePath(file.path, modules).second.substringAfterLast("/") assertTrue(finalPath.startsWith("module")) } @@ -162,7 +162,7 @@ class FuzzierUtilTest { assertEquals(1, modules.size) val file = myFixture.findFileInTempDir("/path/src1/file1") - assertEquals("/file1", FuzzierUtil.extractModulePath(file.path, myFixture.project).first) + assertEquals("/file1", FuzzierUtil.extractModulePath(file.path, modules).first) } @Test @@ -173,7 +173,7 @@ class FuzzierUtilTest { val modules = myFixture.project.service().state.modules assertEquals(1, modules.size) - assertEquals(Pair("/no/such/file", ""), FuzzierUtil.extractModulePath("/no/such/file", myFixture.project)) + assertEquals(Pair("/no/such/file", ""), FuzzierUtil.extractModulePath("/no/such/file", modules)) } @Test From cd1f6ec90ca7f8481d83a5f1fb9e04da97cea462 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:57:18 +0200 Subject: [PATCH 06/10] Refactor for easier testing --- .../com/mituuz/fuzzier/search/Fuzzier.kt | 5 +- .../mituuz/fuzzier/search/FuzzierOpenTabs.kt | 8 +- .../DefaultInitialListModelProvider.kt | 3 +- .../initialview/InitialListModelProvider.kt | 3 +- .../OpenTabsInitialListModelProvider.kt | 13 +- .../OpenTabsInitialListModelProviderTest.kt | 131 ++++++++++++++++++ .../DefaultInitialListModelProviderTest.kt | 6 +- 7 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index 95cdaa64..d98cc773 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -53,8 +53,9 @@ open class Fuzzier : FilesystemAction() { return IntelliJIterationFileCollector(projectState) } - protected open fun getInitialViewProvider(): InitialListModelProvider { + protected open fun getInitialViewProvider(project: Project): InitialListModelProvider { return DefaultInitialListModelProvider( + project, globalState, projectState, ) @@ -154,7 +155,7 @@ open class Fuzzier : FilesystemAction() { component.fileList.setPaintBusy(true) ApplicationManager.getApplication().executeOnPooledThread { try { - val initialListModel = getInitialViewProvider().buildInitialView(project) + val initialListModel = getInitialViewProvider(project).buildInitialView() ApplicationManager.getApplication().invokeLater { component.refreshModel(initialListModel, getCellRenderer()) } diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt index d9ebb59f..e4e1b493 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/FuzzierOpenTabs.kt @@ -24,6 +24,8 @@ package com.mituuz.fuzzier.search +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.intellij.iteration.OpenTabsCollector import com.mituuz.fuzzier.search.initialview.InitialListModelProvider @@ -32,8 +34,10 @@ import com.mituuz.fuzzier.search.initialview.OpenTabsInitialListModelProvider class FuzzierOpenTabs : Fuzzier() { override var popupTitle: String = "Fuzzy Search (Open Tabs)" - override fun getInitialViewProvider(): InitialListModelProvider { - return OpenTabsInitialListModelProvider() + override fun getInitialViewProvider(project: Project): InitialListModelProvider { + val modules = projectState.modules + val openFiles = FileEditorManager.getInstance(project).openFiles + return OpenTabsInitialListModelProvider(modules, openFiles) } override fun createCollector(): IterationFileCollector { 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 2b450357..ac4c1a88 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -35,10 +35,11 @@ import com.mituuz.fuzzier.util.FuzzierUtil import javax.swing.DefaultListModel class DefaultInitialListModelProvider( + val project: Project, val globalState: FuzzierGlobalSettingsService.State, val projectState: FuzzierSettingsService.State, ) : InitialListModelProvider { - override fun buildInitialView(project: Project): DefaultListModel { + override fun buildInitialView(): DefaultListModel { return when (globalState.recentFilesMode) { FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> { getRecentProjectFiles(project) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt index 03b08274..0fd0eb35 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt @@ -24,10 +24,9 @@ package com.mituuz.fuzzier.search.initialview -import com.intellij.openapi.project.Project import com.mituuz.fuzzier.entities.FuzzyContainer import javax.swing.DefaultListModel interface InitialListModelProvider { - fun buildInitialView(project: Project): DefaultListModel + fun buildInitialView(): DefaultListModel } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt index 0f60c9c4..e1946db4 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -25,24 +25,21 @@ package com.mituuz.fuzzier.search.initialview import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.components.service -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.OrderedContainer -import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.util.FuzzierUtil import javax.swing.DefaultListModel class OpenTabsInitialListModelProvider( + private val modules: Map, + private val openFiles: Array ) : InitialListModelProvider { - override fun buildInitialView(project: Project): DefaultListModel { - val fileEditorManager = FileEditorManager.getInstance(project) + override fun buildInitialView(): DefaultListModel { val listModel = DefaultListModel() - val modules = project.service().state.modules ReadAction.run { - for (vf in fileEditorManager.openFiles) { + for (vf in openFiles) { if (!vf.isDirectory) { val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, modules) // Don't add files that do not have a module path in the project diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt new file mode 100644 index 00000000..d13bd1af --- /dev/null +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.ThrowableRunnable +import io.mockk.* +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 + +class OpenTabsInitialListModelProviderTest { + private lateinit var project: Project + + @BeforeEach + fun setUp() { + project = mockk() + mockkStatic(ReadAction::class) + val captor = slot>() + every { ReadAction.run(capture(captor)) } answers { + captor.captured.run() + } + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `buildInitialView with no open files`() { + val provider = OpenTabsInitialListModelProvider(emptyMap(), emptyArray()) + val model = provider.buildInitialView() + assertEquals(0, model.size()) + } + + @Test + fun `buildInitialView with multiple open files`() { + val file1 = mockk() + val file2 = mockk() + + every { file1.isDirectory } returns false + every { file1.path } returns "/project/src/File1.kt" + every { file1.name } returns "File1.kt" + + every { file2.isDirectory } returns false + every { file2.path } returns "/project/src/File2.kt" + every { file2.name } returns "File2.kt" + + val modules = mapOf("project" to "/project/") + val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, file2)) + + val model = provider.buildInitialView() + + assertEquals(2, model.size()) + // Should be in reverse order of openFiles + assertEquals("File2.kt", model.get(0).filename) + assertEquals("src/File2.kt", model.get(0).filePath) + assertEquals("File1.kt", model.get(1).filename) + assertEquals("src/File1.kt", model.get(1).filePath) + } + + @Test + fun `buildInitialView excludes directories`() { + val file1 = mockk() + val dir1 = mockk() + + every { file1.isDirectory } returns false + every { file1.path } returns "/project/src/File1.kt" + every { file1.name } returns "File1.kt" + + every { dir1.isDirectory } returns true + every { dir1.path } returns "/project/src/dir" + every { dir1.name } returns "dir" + + val modules = mapOf("project" to "/project/") + val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, dir1)) + + val model = provider.buildInitialView() + + assertEquals(1, model.size()) + assertEquals("File1.kt", model.get(0).filename) + } + + @Test + fun `buildInitialView excludes files without module path`() { + val file1 = mockk() + val fileOutside = mockk() + + every { file1.isDirectory } returns false + every { file1.path } returns "/project/src/File1.kt" + every { file1.name } returns "File1.kt" + + every { fileOutside.isDirectory } returns false + every { fileOutside.path } returns "/outside/File.kt" + every { fileOutside.name } returns "File.kt" + + val modules = mapOf("project" to "/project/") + val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, fileOutside)) + + val model = provider.buildInitialView() + + assertEquals(1, model.size()) + assertEquals("File1.kt", model.get(0).filename) + } +} diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index 9695bd74..0d98b383 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -65,7 +65,7 @@ class DefaultInitialListModelProviderTest { state = mockk() val globalState = FuzzierGlobalSettingsService.State() globalState.recentFilesMode = FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES - defaultInitialListModelProvider = DefaultInitialListModelProvider(globalState, state) + defaultInitialListModelProvider = DefaultInitialListModelProvider(project, globalState, state) editorHistoryManager = mockk() every { fuzzierSettingsService.state } returns state } @@ -95,7 +95,7 @@ class DefaultInitialListModelProviderTest { val settingsState = FuzzierSettingsService.State() settingsState.modules = mapOf("module" to "/project/path/") - defaultInitialListModelProvider = DefaultInitialListModelProvider(fgss, settingsState) + defaultInitialListModelProvider = DefaultInitialListModelProvider(project, fgss, settingsState) val result = defaultInitialListModelProvider.getRecentProjectFiles(project) @@ -123,7 +123,7 @@ class DefaultInitialListModelProviderTest { val settingsState = FuzzierSettingsService.State() settingsState.modules = mapOf("module" to "/project/path/") - defaultInitialListModelProvider = DefaultInitialListModelProvider(fgss, settingsState) + defaultInitialListModelProvider = DefaultInitialListModelProvider(project, fgss, settingsState) val result = defaultInitialListModelProvider.getRecentProjectFiles(project) From b121aef4a6932533d5b426ada98ce71b8240bf03 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 06:58:55 +0200 Subject: [PATCH 07/10] Remove read action --- .../OpenTabsInitialListModelProvider.kt | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt index e1946db4..2dd86aff 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -24,7 +24,6 @@ package com.mituuz.fuzzier.search.initialview -import com.intellij.openapi.application.ReadAction import com.intellij.openapi.vfs.VirtualFile import com.mituuz.fuzzier.entities.FuzzyContainer import com.mituuz.fuzzier.entities.OrderedContainer @@ -38,19 +37,17 @@ class OpenTabsInitialListModelProvider( override fun buildInitialView(): DefaultListModel { val listModel = DefaultListModel() - ReadAction.run { - for (vf in openFiles) { - if (!vf.isDirectory) { - val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, modules) - // Don't add files that do not have a module path in the project - if (filePathAndModule.second == "") { - continue - } - val orderedContainer = OrderedContainer( - filePathAndModule.first, filePathAndModule.second, vf.name - ) - listModel.add(0, orderedContainer) + for (vf in openFiles) { + if (!vf.isDirectory) { + val filePathAndModule = FuzzierUtil.extractModulePath(vf.path, modules) + // Don't add files that do not have a module path in the project + if (filePathAndModule.second == "") { + continue } + val orderedContainer = OrderedContainer( + filePathAndModule.first, filePathAndModule.second, vf.name + ) + listModel.add(0, orderedContainer) } } From 09f40ef1e378edfcdf00a771d1fc343ce06a9188 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 07:12:36 +0200 Subject: [PATCH 08/10] Move recent file handling to the base interface --- .../com/mituuz/fuzzier/search/Fuzzier.kt | 2 +- .../DefaultInitialListModelProvider.kt | 33 ----------------- .../initialview/InitialListModelProvider.kt | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index d98cc773..ac3f2c1c 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -140,7 +140,7 @@ open class Fuzzier : FilesystemAction() { globalState.newTab ) { if (selectedValue != null) { - DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + InitialListModelProvider.addFileToRecentlySearchedFiles( selectedValue, projectState, globalState 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 ac4c1a88..5f824e87 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -27,7 +27,6 @@ package com.mituuz.fuzzier.search.initialview import com.intellij.openapi.fileEditor.impl.EditorHistoryManager import com.intellij.openapi.project.Project import com.mituuz.fuzzier.entities.FuzzyContainer -import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.OrderedContainer import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService @@ -95,36 +94,4 @@ class DefaultInitialListModelProvider( } return result } - - companion object { - fun addFileToRecentlySearchedFiles( - fuzzyContainer: FuzzyContainer, - projectState: FuzzierSettingsService.State, - globalState: FuzzierGlobalSettingsService.State - ) { - val listModel: DefaultListModel = - projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - - var i = 0 - while (i < listModel.size) { - if (listModel[i].filePath == fuzzyContainer.filePath) { - listModel.remove(i) - } else { - i++ - } - } - - while (listModel.size > globalState.fileListLimit - 1) { - listModel.remove(listModel.size - 1) - } - - if (fuzzyContainer is FuzzyMatchContainer) { - listModel.addElement(fuzzyContainer) - projectState.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) - } - - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt index 0fd0eb35..6791e708 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt @@ -25,8 +25,43 @@ package com.mituuz.fuzzier.search.initialview import com.mituuz.fuzzier.entities.FuzzyContainer +import com.mituuz.fuzzier.entities.FuzzyMatchContainer +import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService +import com.mituuz.fuzzier.settings.FuzzierSettingsService import javax.swing.DefaultListModel interface InitialListModelProvider { fun buildInitialView(): DefaultListModel + + companion object { + fun addFileToRecentlySearchedFiles( + fuzzyContainer: FuzzyContainer, + projectState: FuzzierSettingsService.State, + globalState: FuzzierGlobalSettingsService.State + ) { + val listModel: DefaultListModel = + projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + + var i = 0 + while (i < listModel.size) { + if (listModel[i].filePath == fuzzyContainer.filePath) { + listModel.remove(i) + } else { + i++ + } + } + + while (listModel.size > globalState.fileListLimit - 1) { + listModel.remove(listModel.size - 1) + } + + if (fuzzyContainer is FuzzyMatchContainer) { + listModel.addElement(fuzzyContainer) + projectState.recentlySearchedFiles = + FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) + } + + } + } + } \ No newline at end of file From 044fb37a1a980403cb255f20a24d61125381cda6 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 07:23:45 +0200 Subject: [PATCH 09/10] Please sonarlint, make list model provider a function type --- .../com/mituuz/fuzzier/search/Fuzzier.kt | 5 +- .../DefaultInitialListModelProvider.kt | 2 +- .../initialview/InitialListModelProvider.kt | 39 +----------- .../OpenTabsInitialListModelProvider.kt | 2 +- .../initialview/RecentlySearchedFilesUtil.kt | 59 +++++++++++++++++++ .../OpenTabsInitialListModelProviderTest.kt | 28 +++------ .../DefaultInitialListModelProviderTest.kt | 7 ++- 7 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt index ac3f2c1c..ad402980 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/Fuzzier.kt @@ -39,6 +39,7 @@ import com.mituuz.fuzzier.intellij.iteration.IntelliJIterationFileCollector import com.mituuz.fuzzier.intellij.iteration.IterationFileCollector import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider import com.mituuz.fuzzier.search.initialview.InitialListModelProvider +import com.mituuz.fuzzier.search.initialview.addFileToRecentlySearchedFiles import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.ui.bindings.ActivationBindings import com.mituuz.fuzzier.ui.popup.PopupConfig @@ -140,7 +141,7 @@ open class Fuzzier : FilesystemAction() { globalState.newTab ) { if (selectedValue != null) { - InitialListModelProvider.addFileToRecentlySearchedFiles( + addFileToRecentlySearchedFiles( selectedValue, projectState, globalState @@ -155,7 +156,7 @@ open class Fuzzier : FilesystemAction() { component.fileList.setPaintBusy(true) ApplicationManager.getApplication().executeOnPooledThread { try { - val initialListModel = getInitialViewProvider(project).buildInitialView() + val initialListModel = getInitialViewProvider(project).invoke() ApplicationManager.getApplication().invokeLater { component.refreshModel(initialListModel, getCellRenderer()) } 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 5f824e87..57ded887 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/DefaultInitialListModelProvider.kt @@ -38,7 +38,7 @@ class DefaultInitialListModelProvider( val globalState: FuzzierGlobalSettingsService.State, val projectState: FuzzierSettingsService.State, ) : InitialListModelProvider { - override fun buildInitialView(): DefaultListModel { + override fun invoke(): DefaultListModel { return when (globalState.recentFilesMode) { FuzzierGlobalSettingsService.RecentFilesMode.RECENT_PROJECT_FILES -> { getRecentProjectFiles(project) diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt index 6791e708..683c4763 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/InitialListModelProvider.kt @@ -25,43 +25,6 @@ package com.mituuz.fuzzier.search.initialview import com.mituuz.fuzzier.entities.FuzzyContainer -import com.mituuz.fuzzier.entities.FuzzyMatchContainer -import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService -import com.mituuz.fuzzier.settings.FuzzierSettingsService import javax.swing.DefaultListModel -interface InitialListModelProvider { - fun buildInitialView(): DefaultListModel - - companion object { - fun addFileToRecentlySearchedFiles( - fuzzyContainer: FuzzyContainer, - projectState: FuzzierSettingsService.State, - globalState: FuzzierGlobalSettingsService.State - ) { - val listModel: DefaultListModel = - projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() - - var i = 0 - while (i < listModel.size) { - if (listModel[i].filePath == fuzzyContainer.filePath) { - listModel.remove(i) - } else { - i++ - } - } - - while (listModel.size > globalState.fileListLimit - 1) { - listModel.remove(listModel.size - 1) - } - - if (fuzzyContainer is FuzzyMatchContainer) { - listModel.addElement(fuzzyContainer) - projectState.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) - } - - } - } - -} \ No newline at end of file +typealias InitialListModelProvider = () -> DefaultListModel \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt index 2dd86aff..22aaec4b 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProvider.kt @@ -34,7 +34,7 @@ class OpenTabsInitialListModelProvider( private val modules: Map, private val openFiles: Array ) : InitialListModelProvider { - override fun buildInitialView(): DefaultListModel { + override fun invoke(): DefaultListModel { val listModel = DefaultListModel() for (vf in openFiles) { diff --git a/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt new file mode 100644 index 00000000..25001289 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtil.kt @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +import com.mituuz.fuzzier.entities.FuzzyContainer +import com.mituuz.fuzzier.entities.FuzzyMatchContainer +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 = + projectState.getRecentlySearchedFilesAsFuzzyMatchContainer() + + var i = 0 + while (i < listModel.size) { + if (listModel[i].filePath == fuzzyContainer.filePath) { + listModel.remove(i) + } else { + i++ + } + } + + while (listModel.size > globalState.fileListLimit - 1) { + listModel.remove(listModel.size - 1) + } + + if (fuzzyContainer is FuzzyMatchContainer) { + listModel.addElement(fuzzyContainer) + projectState.recentlySearchedFiles = + FuzzyMatchContainer.SerializedMatchContainer.fromListModel(listModel) + } +} diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt index d13bd1af..805240b9 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/OpenTabsInitialListModelProviderTest.kt @@ -24,29 +24,15 @@ package com.mituuz.fuzzier.search.initialview -import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.util.ThrowableRunnable -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll 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 class OpenTabsInitialListModelProviderTest { - private lateinit var project: Project - - @BeforeEach - fun setUp() { - project = mockk() - mockkStatic(ReadAction::class) - val captor = slot>() - every { ReadAction.run(capture(captor)) } answers { - captor.captured.run() - } - } - @AfterEach fun tearDown() { unmockkAll() @@ -55,7 +41,7 @@ class OpenTabsInitialListModelProviderTest { @Test fun `buildInitialView with no open files`() { val provider = OpenTabsInitialListModelProvider(emptyMap(), emptyArray()) - val model = provider.buildInitialView() + val model = provider.invoke() assertEquals(0, model.size()) } @@ -75,7 +61,7 @@ class OpenTabsInitialListModelProviderTest { val modules = mapOf("project" to "/project/") val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, file2)) - val model = provider.buildInitialView() + val model = provider.invoke() assertEquals(2, model.size()) // Should be in reverse order of openFiles @@ -101,7 +87,7 @@ class OpenTabsInitialListModelProviderTest { val modules = mapOf("project" to "/project/") val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, dir1)) - val model = provider.buildInitialView() + val model = provider.invoke() assertEquals(1, model.size()) assertEquals("File1.kt", model.get(0).filename) @@ -123,7 +109,7 @@ class OpenTabsInitialListModelProviderTest { val modules = mapOf("project" to "/project/") val provider = OpenTabsInitialListModelProvider(modules, arrayOf(file1, fileOutside)) - val model = provider.buildInitialView() + val model = provider.invoke() assertEquals(1, model.size()) assertEquals("File1.kt", model.get(0).filename) diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index 0d98b383..d036cc22 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -31,6 +31,7 @@ import com.intellij.testFramework.TestApplicationManager import com.mituuz.fuzzier.entities.FuzzyMatchContainer import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider +import com.mituuz.fuzzier.search.initialview.addFileToRecentlySearchedFiles import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService.State @@ -182,7 +183,7 @@ class DefaultInitialListModelProviderTest { val container = FuzzyMatchContainer(score, "", "", "", FILE) fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null - DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, fgss @@ -208,7 +209,7 @@ class DefaultInitialListModelProviderTest { fuzzierSettingsServiceInstance.state.recentlySearchedFiles = FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, fgss @@ -236,7 +237,7 @@ class DefaultInitialListModelProviderTest { fuzzierSettingsServiceInstance.state.recentlySearchedFiles = FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - DefaultInitialListModelProvider.addFileToRecentlySearchedFiles( + addFileToRecentlySearchedFiles( container, fuzzierSettingsServiceInstance.state, fgss From d731907df875a11331c77153e559bfe2eedfd089 Mon Sep 17 00:00:00 2001 From: Mitja Date: Fri, 13 Feb 2026 07:25:56 +0200 Subject: [PATCH 10/10] Move tests to a correct file --- .../RecentlySearchedFilesUtilTest.kt | 118 ++++++++++++++++++ .../DefaultInitialListModelProviderTest.kt | 74 ----------- 2 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt diff --git a/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt new file mode 100644 index 00000000..d7ff4831 --- /dev/null +++ b/src/test/kotlin/com/mituuz/fuzzier/search/initialview/RecentlySearchedFilesUtilTest.kt @@ -0,0 +1,118 @@ +/* + * MIT License + * + * Copyright (c) 2025 Mitja Leino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.mituuz.fuzzier.search.initialview + +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 +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) + private var testApplicationManager: TestApplicationManager = TestApplicationManager.getInstance() + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @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) + + fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null + addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) + assertNotNull(fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer()) + assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) + } + + @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) + + val largeList: DefaultListModel = DefaultListModel() + for (i in 0..25) { + largeList.addElement(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) + } + + fgss.fileListLimit = fileListLimit + + fuzzierSettingsServiceInstance.state.recentlySearchedFiles = + FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) + addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) + assertEquals( + fileListLimit, + fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size + ) + } + + @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) + + val largeList: DefaultListModel = DefaultListModel() + repeat(26) { + largeList.addElement(FuzzyMatchContainer(score, "", "", "", FILE)) + } + + fgss.fileListLimit = fileListLimit + + fuzzierSettingsServiceInstance.state.recentlySearchedFiles = + FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) + addFileToRecentlySearchedFiles( + container, + fuzzierSettingsServiceInstance.state, + fgss + ) + assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) + } +} diff --git a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt index d036cc22..dec7861e 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/util/DefaultInitialListModelProviderTest.kt @@ -23,15 +23,12 @@ */ package com.mituuz.fuzzier.util -import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.impl.EditorHistoryManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.TestApplicationManager import com.mituuz.fuzzier.entities.FuzzyMatchContainer -import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FileType.FILE import com.mituuz.fuzzier.search.initialview.DefaultInitialListModelProvider -import com.mituuz.fuzzier.search.initialview.addFileToRecentlySearchedFiles import com.mituuz.fuzzier.settings.FuzzierGlobalSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService import com.mituuz.fuzzier.settings.FuzzierSettingsService.State @@ -41,7 +38,6 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll 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.BeforeEach import org.junit.jupiter.api.Test import javax.swing.DefaultListModel @@ -174,74 +170,4 @@ class DefaultInitialListModelProviderTest { assertEquals(1, result.size) } - - @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) - - fuzzierSettingsServiceInstance.state.recentlySearchedFiles = null - addFileToRecentlySearchedFiles( - container, - fuzzierSettingsServiceInstance.state, - fgss - ) - assertNotNull(fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer()) - assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) - } - - @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) - - val largeList: DefaultListModel = DefaultListModel() - for (i in 0..25) { - largeList.addElement(FuzzyMatchContainer(score, "" + i, "" + i, "", FILE)) - } - - fgss.fileListLimit = fileListLimit - - fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - addFileToRecentlySearchedFiles( - container, - fuzzierSettingsServiceInstance.state, - fgss - ) - assertEquals( - fileListLimit, - fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size - ) - } - - @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) - - val largeList: DefaultListModel = DefaultListModel() - repeat(26) { - largeList.addElement(FuzzyMatchContainer(score, "", "", "", FILE)) - } - - fgss.fileListLimit = fileListLimit - - fuzzierSettingsServiceInstance.state.recentlySearchedFiles = - FuzzyMatchContainer.SerializedMatchContainer.fromListModel(largeList) - addFileToRecentlySearchedFiles( - container, - fuzzierSettingsServiceInstance.state, - fgss - ) - assertEquals(1, fuzzierSettingsServiceInstance.state.getRecentlySearchedFilesAsFuzzyMatchContainer().size) - } } \ No newline at end of file