refactor: popup suggestions strategy

This commit is contained in:
Carl-Robert Linnupuu 2024-07-27 11:19:26 +03:00
parent 44db3495b4
commit 2986c42ad9
3 changed files with 125 additions and 164 deletions

View file

@ -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<SuggestionItem>
}
class FileSuggestionStrategy : SuggestionStrategy {
override suspend fun getSuggestions(
project: Project,
searchText: String?
): List<SuggestionItem> {
if (searchText == null) {
val projectFileIndex = project.service<ProjectFileIndex>()
return readAction {
project.service<FileEditorManager>().openFiles
.filter { projectFileIndex.isInContent(it) }
.take(10)
.map { SuggestionItem.FileItem(File(it.path)) }
}
}
return project.service<FileSearchService>().searchFiles(searchText)
.take(10)
.map { SuggestionItem.FileItem(File(it)) }
}
}
class FolderSuggestionStrategy : SuggestionStrategy {
private val projectFoldersCache = mutableMapOf<Project, List<String>>()
override suspend fun getSuggestions(
project: Project,
searchText: String?
): List<SuggestionItem> {
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<String> {
return projectFoldersCache.getOrPut(project) {
findProjectFolders(project)
}
}
private suspend fun findProjectFolders(project: Project): List<String> =
withContext(Dispatchers.IO) {
val uniqueFolders = mutableSetOf<String>()
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<ProjectFileIndex>().iterateContent(iterator)
uniqueFolders.toList()
}
}
class PersonaSuggestionStrategy : SuggestionStrategy {
override suspend fun getSuggestions(
project: Project,
searchText: String?
): List<SuggestionItem> {
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<SuggestionItem> = emptyList()
}

View file

@ -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<SuggestionItem>,
)
fun updateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
searchText: String,
)
}
class FileSuggestionActionStrategy : SuggestionUpdateStrategy {
override fun populateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
) {
val projectFileIndex = project.service<ProjectFileIndex>()
CoroutineScope(Dispatchers.Default).launch {
val openFilePaths = project.service<FileEditorManager>().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<SuggestionItem>,
searchText: String,
) {
val filePaths = project.service<FileSearchService>().searchFiles(searchText).take(10)
listModel.clear()
listModel.addAll(filePaths.map { SuggestionItem.FileItem(File(it)) })
}
}
class FolderSuggestionActionStrategy : SuggestionUpdateStrategy {
private val projectFoldersCache = mutableMapOf<Project, List<String>>()
override fun populateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>
) {
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<SuggestionItem>,
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<String> {
return projectFoldersCache.getOrPut(project) {
findProjectFolders(project)
}
}
private suspend fun findProjectFolders(project: Project): List<String> =
withContext(Dispatchers.IO) {
val uniqueFolders = mutableSetOf<String>()
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<ProjectFileIndex>().iterateContent(iterator)
uniqueFolders.toList()
}
}
class PersonaSuggestionActionStrategy : SuggestionUpdateStrategy {
override fun populateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
) {
listModel.clear()
listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions(null))
}
override fun updateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
searchText: String,
) {
listModel.clear()
listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions {
it.name.contains(
searchText,
true
)
})
}
}
class DefaultSuggestionActionStrategy : SuggestionUpdateStrategy {
override fun populateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
) {
}
override fun updateSuggestions(
project: Project,
listModel: DefaultListModel<SuggestionItem>,
searchText: String,
) {
}
}

View file

@ -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<SuggestionItem.ActionItem> = 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)