From 2986c42ad98a47b2fd6a26647f0ac69210ca84f7 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Sat, 27 Jul 2024 11:19:26 +0300 Subject: [PATCH] refactor: popup suggestions strategy --- .../codegpt/ui/textarea/SuggestionStrategy.kt | 102 ++++++++++++ .../ui/textarea/SuggestionUpdateStrategy.kt | 153 ------------------ .../ui/textarea/SuggestionsPopupManager.kt | 34 ++-- 3 files changed, 125 insertions(+), 164 deletions(-) create mode 100644 src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionStrategy.kt delete mode 100644 src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionUpdateStrategy.kt diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionStrategy.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionStrategy.kt new file mode 100644 index 00000000..b81941b5 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionStrategy.kt @@ -0,0 +1,102 @@ +package ee.carlrobert.codegpt.ui.textarea + +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.roots.ContentIterator +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VirtualFile +import ee.carlrobert.codegpt.util.ResourceUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import java.nio.file.Path + +interface SuggestionStrategy { + suspend fun getSuggestions(project: Project, searchText: String? = null): List +} + +class FileSuggestionStrategy : SuggestionStrategy { + override suspend fun getSuggestions( + project: Project, + searchText: String? + ): List { + if (searchText == null) { + val projectFileIndex = project.service() + return readAction { + project.service().openFiles + .filter { projectFileIndex.isInContent(it) } + .take(10) + .map { SuggestionItem.FileItem(File(it.path)) } + } + } + return project.service().searchFiles(searchText) + .take(10) + .map { SuggestionItem.FileItem(File(it)) } + } +} + +class FolderSuggestionStrategy : SuggestionStrategy { + private val projectFoldersCache = mutableMapOf>() + + override suspend fun getSuggestions( + project: Project, + searchText: String? + ): List { + if (searchText == null) { + return getProjectFolders(project) + .take(10) + .map { SuggestionItem.FolderItem(Path.of(it).toFile()) } + } + return getProjectFolders(project) + .filter { it.contains(searchText, ignoreCase = true) } + .take(10) + .map { SuggestionItem.FolderItem(Path.of(it).toFile()) } + } + + private suspend fun getProjectFolders(project: Project): List { + return projectFoldersCache.getOrPut(project) { + findProjectFolders(project) + } + } + + private suspend fun findProjectFolders(project: Project): List = + withContext(Dispatchers.IO) { + val uniqueFolders = mutableSetOf() + val iterator = ContentIterator { file: VirtualFile -> + if (file.isDirectory && !file.name.startsWith(".")) { + val folderPath = file.path + if (uniqueFolders.none { it.startsWith(folderPath) }) { + uniqueFolders.removeAll { it.startsWith(folderPath) } + uniqueFolders.add(folderPath) + } + } + true + } + + project.service().iterateContent(iterator) + uniqueFolders.toList() + } +} + +class PersonaSuggestionStrategy : SuggestionStrategy { + override suspend fun getSuggestions( + project: Project, + searchText: String? + ): List { + if (searchText == null) { + return ResourceUtil.getFilteredPersonaSuggestions(null) + } + return ResourceUtil.getFilteredPersonaSuggestions { + it.name.contains(searchText, true) + } + } +} + +class DefaultSuggestionStrategy : SuggestionStrategy { + override suspend fun getSuggestions( + project: Project, + searchText: String? + ): List = emptyList() +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionUpdateStrategy.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionUpdateStrategy.kt deleted file mode 100644 index b9877da0..00000000 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionUpdateStrategy.kt +++ /dev/null @@ -1,153 +0,0 @@ -package ee.carlrobert.codegpt.ui.textarea - -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.roots.ContentIterator -import com.intellij.openapi.roots.ProjectFileIndex -import com.intellij.openapi.vfs.VirtualFile -import ee.carlrobert.codegpt.util.ResourceUtil -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File -import java.nio.file.Path -import javax.swing.DefaultListModel - -interface SuggestionUpdateStrategy { - fun populateSuggestions( - project: Project, - listModel: DefaultListModel, - ) - - fun updateSuggestions( - project: Project, - listModel: DefaultListModel, - searchText: String, - ) -} - -class FileSuggestionActionStrategy : SuggestionUpdateStrategy { - override fun populateSuggestions( - project: Project, - listModel: DefaultListModel, - ) { - val projectFileIndex = project.service() - CoroutineScope(Dispatchers.Default).launch { - val openFilePaths = project.service().openFiles - .filter { readAction { projectFileIndex.isInContent(it) } } - .take(10) - .map { file -> file.path } - listModel.clear() - listModel.addAll(openFilePaths.map { SuggestionItem.FileItem(File(it)) }) - } - } - - override fun updateSuggestions( - project: Project, - listModel: DefaultListModel, - searchText: String, - ) { - val filePaths = project.service().searchFiles(searchText).take(10) - listModel.clear() - listModel.addAll(filePaths.map { SuggestionItem.FileItem(File(it)) }) - } -} - -class FolderSuggestionActionStrategy : SuggestionUpdateStrategy { - private val projectFoldersCache = mutableMapOf>() - - override fun populateSuggestions( - project: Project, - listModel: DefaultListModel - ) { - CoroutineScope(Dispatchers.Default).launch { - val folderPaths = getProjectFolders(project) - .take(10) - .map { SuggestionItem.FolderItem(Path.of(it).toFile()) } - listModel.clear() - listModel.addAll(folderPaths) - } - } - - override fun updateSuggestions( - project: Project, - listModel: DefaultListModel, - searchText: String - ) { - CoroutineScope(Dispatchers.Default).launch { - val filteredFolders = getProjectFolders(project) - .filter { it.contains(searchText, ignoreCase = true) } - .take(10) - .map { SuggestionItem.FolderItem(Path.of(it).toFile()) } - listModel.clear() - listModel.addAll(filteredFolders) - } - } - - private suspend fun getProjectFolders(project: Project): List { - return projectFoldersCache.getOrPut(project) { - findProjectFolders(project) - } - } - - private suspend fun findProjectFolders(project: Project): List = - withContext(Dispatchers.IO) { - val uniqueFolders = mutableSetOf() - val iterator = ContentIterator { file: VirtualFile -> - if (file.isDirectory && !file.name.startsWith(".")) { - val folderPath = file.path - if (uniqueFolders.none { it.startsWith(folderPath) }) { - uniqueFolders.removeAll { it.startsWith(folderPath) } - uniqueFolders.add(folderPath) - } - } - true - } - - project.service().iterateContent(iterator) - uniqueFolders.toList() - } -} - -class PersonaSuggestionActionStrategy : SuggestionUpdateStrategy { - - override fun populateSuggestions( - project: Project, - listModel: DefaultListModel, - ) { - listModel.clear() - listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions(null)) - } - - override fun updateSuggestions( - project: Project, - listModel: DefaultListModel, - searchText: String, - ) { - listModel.clear() - listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions { - it.name.contains( - searchText, - true - ) - }) - } -} - -class DefaultSuggestionActionStrategy : SuggestionUpdateStrategy { - override fun populateSuggestions( - project: Project, - listModel: DefaultListModel, - ) { - } - - override fun updateSuggestions( - project: Project, - listModel: DefaultListModel, - searchText: String, - ) { - } -} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt index de869e9b..c76c5ce1 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.ui.textarea import com.intellij.icons.AllIcons +import com.intellij.openapi.application.runInEdt import com.intellij.openapi.components.service import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.Project @@ -19,6 +20,9 @@ import ee.carlrobert.codegpt.CodeGPTBundle import ee.carlrobert.codegpt.settings.persona.PersonaDetails import ee.carlrobert.codegpt.settings.persona.PersonaSettings import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import java.awt.Dimension import java.awt.Point import java.io.File @@ -64,7 +68,7 @@ class SuggestionsPopupManager( private val textPane: CustomTextPane, ) { - private var currentActionStrategy: SuggestionUpdateStrategy = DefaultSuggestionActionStrategy() + private var currentActionStrategy: SuggestionStrategy = DefaultSuggestionStrategy() private val appliedActions: MutableList = mutableListOf() private var popup: JBPopup? = null private var originalLocation: Point? = null @@ -115,10 +119,18 @@ class SuggestionsPopupManager( list.selectNext() } - fun updateSuggestions(searchText: String) { - currentActionStrategy.updateSuggestions(project, listModel, searchText) - list.revalidate() - list.repaint() + fun updateSuggestions(searchText: String? = null) { + val suggestions = runBlocking { + withContext(Dispatchers.Default) { + currentActionStrategy.getSuggestions(project, searchText) + } + } + runInEdt { + listModel.clear() + listModel.addAll(suggestions) + list.revalidate() + list.repaint() + } } fun reset(clearPrevious: Boolean = true) { @@ -143,22 +155,22 @@ class SuggestionsPopupManager( appliedActions.add(item) currentActionStrategy = when (item.action) { DefaultAction.FILES -> { - FileSuggestionActionStrategy() + FileSuggestionStrategy() } DefaultAction.FOLDERS -> { - FolderSuggestionActionStrategy() + FolderSuggestionStrategy() } DefaultAction.PERSONAS -> { - PersonaSuggestionActionStrategy() + PersonaSuggestionStrategy() } else -> { - DefaultSuggestionActionStrategy() + DefaultSuggestionStrategy() } } - currentActionStrategy.populateSuggestions(project, listModel) + updateSuggestions() textPane.appendHighlightedText(item.action.code, withWhitespace = false) textPane.requestFocus() } @@ -238,7 +250,7 @@ class SuggestionsPopupManager( .setMinSize(Dimension(480, 30)) .setCancelCallback { originalLocation = null - currentActionStrategy = DefaultSuggestionActionStrategy() + currentActionStrategy = DefaultSuggestionStrategy() true } .setResizable(true)