mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-20 01:02:02 +00:00
feat: Added code structure analysis with depth configuration and improved tag handling (#1045)
* feat: add Kotlin inferred type analyzer * feat: implemented a queue with support for maximum crawl depth * feat: added the depth of analysis setting to end the chat * feat: added the depth of analysis setting to code completion * feat: add tag for code analysis # Conflicts: # src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/PromptTextField.kt * feat: changed priority of EditorTagDetails and FileTagDetails If we added a file when opening a tab, and then added the same file through the "Include files in Prompt..." menu, it will not be in the selected state. --------- Co-authored-by: alexander.korovin <alexander.korovin@vk.team> Co-authored-by: Carl-Robert Linnupuu <carlrobertoh@gmail.com>
This commit is contained in:
parent
aef332e486
commit
2ead45efde
21 changed files with 318 additions and 52 deletions
|
|
@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.toolwindow.chat.structure.data
|
|||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
|
|
@ -13,6 +14,7 @@ import com.intellij.psi.PsiFile
|
|||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.util.io.await
|
||||
import ee.carlrobert.codegpt.psistructure.PsiStructureProvider
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationStateListener
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.*
|
||||
import ee.carlrobert.codegpt.util.coroutines.CoroutineDispatchers
|
||||
|
|
@ -54,10 +56,29 @@ class PsiStructureRepository(
|
|||
}
|
||||
|
||||
override fun onTagSelectionChanged(tag: TagDetails) {
|
||||
(tag as? CodeAnalyzeTagDetails)?.let {
|
||||
handleCodeAnalyzeTag(it)
|
||||
}
|
||||
|
||||
val tags = tagManager.getTags().getPsiAnalyzedTags()
|
||||
update(tags)
|
||||
}
|
||||
|
||||
private fun handleCodeAnalyzeTag(tag: CodeAnalyzeTagDetails) {
|
||||
if (!tag.selected) {
|
||||
_structureState.value = PsiStructureState.Content(emptySet(), emptySet())
|
||||
service<ConfigurationSettings>().state
|
||||
.chatCompletionSettings
|
||||
.psiStructureEnabled = false
|
||||
disable()
|
||||
} else {
|
||||
service<ConfigurationSettings>().state
|
||||
.chatCompletionSettings
|
||||
.psiStructureEnabled = true
|
||||
enable()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePsiStructureIfNeeded() {
|
||||
val tags = tagManager.getTags().getPsiAnalyzedTags()
|
||||
if (isNeedUpdatePsiStructure(tags)) {
|
||||
|
|
@ -99,6 +120,8 @@ class PsiStructureRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private var analyzePsiDepth = Int.MAX_VALUE
|
||||
|
||||
init {
|
||||
Disposer.register(parentDisposable, coroutineScope)
|
||||
tagManager.addListener(tagsListener)
|
||||
|
|
@ -113,7 +136,20 @@ class PsiStructureRepository(
|
|||
connection.subscribe(
|
||||
ConfigurationStateListener.TOPIC,
|
||||
ConfigurationStateListener { newState ->
|
||||
tagManager.getTags()
|
||||
.filterIsInstance<CodeAnalyzeTagDetails>()
|
||||
.forEach { tagManager.remove(it) }
|
||||
|
||||
if (tagManager.getTags().any { it is EditorTagDetails || it is FileTagDetails }) {
|
||||
tagManager.addTag(
|
||||
CodeAnalyzeTagDetails().apply {
|
||||
selected = newState.chatCompletionSettings.psiStructureEnabled
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (newState.chatCompletionSettings.psiStructureEnabled) {
|
||||
analyzePsiDepth = newState.chatCompletionSettings.psiStructureAnalyzeDepth
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
|
|
@ -164,7 +200,7 @@ class PsiStructureRepository(
|
|||
.await()
|
||||
|
||||
val virtualFilesToRemoveFromStructure = tags.getExcludedVirtualFiles()
|
||||
val result = psiStructureProvider.get(psiFiles)
|
||||
val result = psiStructureProvider.get(psiFiles, analyzePsiDepth)
|
||||
.filter { classStructure ->
|
||||
!virtualFilesToRemoveFromStructure.contains(classStructure.virtualFile)
|
||||
}
|
||||
|
|
@ -197,8 +233,9 @@ class PsiStructureRepository(
|
|||
is EmptyTagDetails -> null
|
||||
is WebTagDetails -> null
|
||||
is ImageTagDetails -> null
|
||||
is CodeAnalyzeTagDetails -> null
|
||||
}
|
||||
|
||||
|
||||
virtualFile?.takeIf { it.isValid && it.exists()}
|
||||
}
|
||||
}
|
||||
|
|
@ -223,6 +260,7 @@ class PsiStructureRepository(
|
|||
is EmptyTagDetails -> false
|
||||
is WebTagDetails -> false
|
||||
is ImageTagDetails -> false
|
||||
is CodeAnalyzeTagDetails -> false
|
||||
}
|
||||
}
|
||||
.toSet()
|
||||
|
|
@ -249,8 +287,9 @@ class PsiStructureRepository(
|
|||
is EmptyTagDetails -> null
|
||||
is WebTagDetails -> null
|
||||
is ImageTagDetails -> null
|
||||
is CodeAnalyzeTagDetails -> null
|
||||
}
|
||||
|
||||
|
||||
virtualFile?.takeIf { it.isValid && it.exists()}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ public class TotalTokensPanel extends JPanel {
|
|||
if (ConfigurationSettings.getState().getChatCompletionSettings()
|
||||
.getPsiStructureEnabled()) {
|
||||
updatePsiTokenCount(psiTokens);
|
||||
} else {
|
||||
updatePsiTokenCount(0);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ object InfillRequestUtil {
|
|||
}
|
||||
|
||||
if (service<ConfigurationSettings>().state.codeCompletionSettings.collectDependencyStructure) {
|
||||
val psiStructure = PsiStructureProvider().get(listOf(request.file))
|
||||
val depth = service<ConfigurationSettings>().state.codeCompletionSettings.psiStructureAnalyzeDepth
|
||||
val psiStructure = PsiStructureProvider().get(listOf(request.file), depth)
|
||||
if (psiStructure.isNotEmpty()) {
|
||||
infillRequestBuilder.addDependenciesStructure(psiStructure)
|
||||
infillRequestBuilder.addRepositoryName(psiStructure.first().repositoryName)
|
||||
|
|
|
|||
|
|
@ -17,26 +17,15 @@ import ee.carlrobert.codegpt.psistructure.models.MethodStructure
|
|||
import ee.carlrobert.codegpt.psistructure.models.ParameterInfo
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtClassBody
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtConstructor
|
||||
import org.jetbrains.kotlin.psi.KtEnumEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtModifierListOwner
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtObjectDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtVariableDeclaration
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinFileAnalyzer(
|
||||
private val psiFileQueue: PsiFileQueue,
|
||||
private val psiFileQueue: PsiFileDepthQueue,
|
||||
private val ktFile: KtFile,
|
||||
) {
|
||||
|
||||
private val psiManager = PsiManager.getInstance(ktFile.project)
|
||||
private val kotlinPropertyAnalyzer = KotlinPropertyAnalyzer()
|
||||
|
||||
private val filePackageTypes: Map<String, String> by lazy {
|
||||
val types = mutableListOf<String>()
|
||||
|
|
@ -259,7 +248,7 @@ class KotlinFileAnalyzer(
|
|||
}
|
||||
|
||||
private fun analyzeProperty(property: KtProperty): FieldStructure {
|
||||
val type = property.typeReference?.text ?: "TypeUnknown"
|
||||
val type = property.typeReference?.text ?: kotlinPropertyAnalyzer.resolveInferredType(property)
|
||||
val resolvedType = resolveType(type)
|
||||
val modifierList = getModifiers(property)
|
||||
return FieldStructure(property.name ?: "", resolvedType, modifierList)
|
||||
|
|
@ -335,7 +324,7 @@ class KotlinFileAnalyzer(
|
|||
}
|
||||
|
||||
foundKtFiles.forEach { psiFile ->
|
||||
psiFileQueue.put(psiFile)
|
||||
psiFileQueue.put(psiFile, ktFile.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package ee.carlrobert.codegpt.psistructure
|
||||
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinPropertyAnalyzer {
|
||||
|
||||
fun resolveInferredType(property: KtProperty): String {
|
||||
return try {
|
||||
val initializer = property.initializer
|
||||
if (initializer != null) {
|
||||
val bindingContext = initializer.analyze()
|
||||
val type = bindingContext.getType(initializer)?.toString()
|
||||
if (type != null) {
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
val delExpression = property.delegate?.expression
|
||||
if (delExpression != null) {
|
||||
val delegatedType = resolveDelegatedPropertyType(property)
|
||||
if (delegatedType != TYPE_UNKNOWN) {
|
||||
return delegatedType
|
||||
}
|
||||
}
|
||||
|
||||
val getterType = resolvePropertyWithGetter(property)
|
||||
if (getterType != TYPE_UNKNOWN) {
|
||||
return getterType
|
||||
}
|
||||
|
||||
val expressionType = resolveExpressionType(property)
|
||||
if (expressionType != TYPE_UNKNOWN) {
|
||||
return expressionType
|
||||
}
|
||||
TYPE_UNKNOWN
|
||||
} catch (e: Exception) {
|
||||
TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun resolveDelegatedPropertyType(property: KtProperty): String {
|
||||
return property.delegate?.expression?.let { delegateExpr ->
|
||||
when (val initializer = getDelegateInitializer(delegateExpr)) {
|
||||
is KtLambdaExpression -> resolveLambdaReturnType(initializer)
|
||||
else -> resolveExpressionType(initializer)
|
||||
}
|
||||
} ?: TYPE_UNKNOWN
|
||||
}
|
||||
|
||||
private fun getDelegateInitializer(expr: KtExpression): KtExpression? {
|
||||
return when (expr) {
|
||||
is KtCallExpression -> expr.lambdaArguments.firstOrNull()?.getLambdaExpression()
|
||||
is KtBinaryExpression -> expr.right?.let(::getDelegateInitializer)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveLambdaReturnType(lambda: KtLambdaExpression): String {
|
||||
return try {
|
||||
val lastExpr = lambda.bodyExpression?.statements?.lastOrNull()
|
||||
val bindingContext = lambda.analyze()
|
||||
lastExpr?.let { bindingContext.getType(it)?.toString() } ?: TYPE_UNKNOWN
|
||||
} catch (e: Exception) {
|
||||
TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolvePropertyWithGetter(property: KtProperty): String {
|
||||
return property.getter?.bodyExpression?.let { expr ->
|
||||
try {
|
||||
val bindingContext = expr.analyze()
|
||||
bindingContext.getType(expr)?.toString() ?: TYPE_UNKNOWN
|
||||
} catch (e: Exception) {
|
||||
TYPE_UNKNOWN
|
||||
}
|
||||
} ?: TYPE_UNKNOWN
|
||||
}
|
||||
|
||||
private fun resolveExpressionType(expression: KtExpression?): String {
|
||||
if (expression == null) return TYPE_UNKNOWN
|
||||
|
||||
if (expression is KtDotQualifiedExpression) {
|
||||
return resolveQualifiedChain(expression)
|
||||
}
|
||||
|
||||
return try {
|
||||
val bindingContext = expression.analyze()
|
||||
val ktType = bindingContext.getType(expression)
|
||||
ktType?.toString() ?: TYPE_UNKNOWN
|
||||
} catch (e: Exception) {
|
||||
TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveQualifiedChain(expr: KtDotQualifiedExpression): String {
|
||||
return buildString {
|
||||
var currentExpr: KtExpression? = expr
|
||||
while (currentExpr is KtDotQualifiedExpression) {
|
||||
val selector = currentExpr.selectorExpression?.text ?: break
|
||||
append(".").append(selector)
|
||||
currentExpr = currentExpr.receiverExpression
|
||||
}
|
||||
val rootType = currentExpr?.let(::resolveExpressionType) ?: TYPE_UNKNOWN
|
||||
replaceFirst(Regex("."), rootType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TYPE_UNKNOWN = "TypeUnknown"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ee.carlrobert.codegpt.psistructure
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
data class PsiDepthFile(
|
||||
val psiFile: PsiFile,
|
||||
val depth: Int,
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package ee.carlrobert.codegpt.psistructure
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
class PsiFileDepthQueue(
|
||||
initial: List<PsiFile>,
|
||||
private val maxDepth: Int = -1,
|
||||
) {
|
||||
|
||||
private val psiFiles = mutableSetOf<PsiDepthFile>().apply {
|
||||
addAll(initial.map { PsiDepthFile(it, 0) })
|
||||
}
|
||||
|
||||
private val queue = ArrayDeque(initial.map { PsiDepthFile(it, 0) })
|
||||
|
||||
@Synchronized
|
||||
fun pop(): PsiFile? {
|
||||
while (queue.isNotEmpty()) {
|
||||
val first = queue.first()
|
||||
if (maxDepth == -1 || first.depth <= maxDepth) {
|
||||
return queue.removeFirst().psiFile
|
||||
} else {
|
||||
queue.removeFirst()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun put(psiFile: PsiFile, baseFileName: String) {
|
||||
if (psiFiles.any { it.psiFile.name == psiFile.name }) return
|
||||
val baseFileDepth = psiFiles.find { it.psiFile.name == baseFileName }?.depth ?: 0
|
||||
val newItem = PsiDepthFile(psiFile, baseFileDepth + 1)
|
||||
queue.add(newItem)
|
||||
psiFiles.add(newItem)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package ee.carlrobert.codegpt.psistructure
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
class PsiFileQueue(
|
||||
initial: List<PsiFile>
|
||||
) {
|
||||
|
||||
private val queue = ArrayDeque(initial)
|
||||
|
||||
@Synchronized
|
||||
fun pop(): PsiFile? =
|
||||
queue.removeFirstOrNull()
|
||||
|
||||
@Synchronized
|
||||
fun put(psiFile: PsiFile) {
|
||||
queue.add(psiFile)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,10 @@ import kotlin.coroutines.cancellation.CancellationException
|
|||
|
||||
class PsiStructureProvider {
|
||||
|
||||
suspend fun get(psiFiles: List<PsiFile>): Set<ClassStructure> {
|
||||
suspend fun get(
|
||||
psiFiles: List<PsiFile>,
|
||||
analyzeDepth: Int,
|
||||
): Set<ClassStructure> {
|
||||
var result: Set<ClassStructure>? = null
|
||||
var attempts = 0
|
||||
val maxAttempts = 5
|
||||
|
|
@ -35,17 +38,23 @@ class PsiStructureProvider {
|
|||
val future = ReadAction.nonBlocking<Set<ClassStructure>> {
|
||||
val classStructureSet = mutableSetOf<ClassStructure>()
|
||||
val processedPsiFiles = mutableSetOf<PsiFile?>()
|
||||
val psiFileQueue = PsiFileQueue(psiFiles)
|
||||
val psiFileDepthQueue = PsiFileDepthQueue(psiFiles, analyzeDepth)
|
||||
|
||||
while (true) {
|
||||
coroutineContext.ensureActive()
|
||||
|
||||
val psiFile = psiFileQueue.pop()
|
||||
val psiFile = psiFileDepthQueue.pop()
|
||||
|
||||
when {
|
||||
processedPsiFiles.contains(psiFile) -> Unit
|
||||
|
||||
kotlinFileAnalyzerAvailable && psiFile is KtFile -> {
|
||||
classStructureSet.addAll(KotlinFileAnalyzer(psiFileQueue, psiFile).analyze())
|
||||
classStructureSet.addAll(
|
||||
KotlinFileAnalyzer(
|
||||
psiFileQueue = psiFileDepthQueue,
|
||||
ktFile = psiFile,
|
||||
).analyze()
|
||||
)
|
||||
processedPsiFiles.add(psiFile)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package ee.carlrobert.codegpt.settings.configuration
|
|||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.ui.DialogPanel
|
||||
import com.intellij.ui.PortField
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.components.fields.IntegerField
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
|
|
@ -19,6 +22,10 @@ class ChatCompletionConfigurationForm {
|
|||
service<ConfigurationSettings>().state.chatCompletionSettings.psiStructureEnabled
|
||||
)
|
||||
|
||||
private val psiStructureAnalyzeDepthField = PortField().apply {
|
||||
number = service<ConfigurationSettings>().state.chatCompletionSettings.psiStructureAnalyzeDepth
|
||||
}
|
||||
|
||||
fun createPanel(): DialogPanel {
|
||||
return panel {
|
||||
row {
|
||||
|
|
@ -29,18 +36,27 @@ class ChatCompletionConfigurationForm {
|
|||
cell(psiStructureCheckBox)
|
||||
.comment(CodeGPTBundle.get("configurationConfigurable.section.chatCompletion.psiStructure.description"))
|
||||
}
|
||||
row {
|
||||
label(
|
||||
CodeGPTBundle.get("configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.title"),
|
||||
)
|
||||
cell(psiStructureAnalyzeDepthField)
|
||||
.comment(CodeGPTBundle.get("configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.comment"))
|
||||
}
|
||||
}.withBorder(JBUI.Borders.emptyLeft(16))
|
||||
}
|
||||
|
||||
fun resetForm(prevState: ChatCompletionSettingsState) {
|
||||
editorContextTagCheckBox.isSelected = prevState.editorContextTagEnabled
|
||||
psiStructureCheckBox.isSelected = prevState.psiStructureEnabled
|
||||
psiStructureAnalyzeDepthField.number = prevState.psiStructureAnalyzeDepth
|
||||
}
|
||||
|
||||
fun getFormState(): ChatCompletionSettingsState {
|
||||
return ChatCompletionSettingsState().apply {
|
||||
this.editorContextTagEnabled = editorContextTagCheckBox.isSelected
|
||||
this.psiStructureEnabled = psiStructureCheckBox.isSelected
|
||||
this.psiStructureAnalyzeDepth = psiStructureAnalyzeDepthField.number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package ee.carlrobert.codegpt.settings.configuration
|
|||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.ui.DialogPanel
|
||||
import com.intellij.ui.PortField
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.fields.IntegerField
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
|
|
@ -22,6 +24,10 @@ class CodeCompletionConfigurationForm {
|
|||
service<ConfigurationSettings>().state.codeCompletionSettings.collectDependencyStructure
|
||||
)
|
||||
|
||||
private val psiStructureAnalyzeDepthField = PortField().apply {
|
||||
number = service<ConfigurationSettings>().state.codeCompletionSettings.psiStructureAnalyzeDepth
|
||||
}
|
||||
|
||||
fun createPanel(): DialogPanel {
|
||||
return panel {
|
||||
row {
|
||||
|
|
@ -36,12 +42,20 @@ class CodeCompletionConfigurationForm {
|
|||
cell(collectDependencyStructureBox)
|
||||
.comment(CodeGPTBundle.get("configurationConfigurable.section.codeCompletion.collectDependencyStructure.description"))
|
||||
}
|
||||
row {
|
||||
label(
|
||||
CodeGPTBundle.get("configurationConfigurable.section.codeCompletion.analyzeDepth.title"),
|
||||
)
|
||||
cell(psiStructureAnalyzeDepthField)
|
||||
.comment(CodeGPTBundle.get("configurationConfigurable.section.codeCompletion.analyzeDepth.comment"))
|
||||
}
|
||||
}.withBorder(JBUI.Borders.emptyLeft(16))
|
||||
}
|
||||
|
||||
fun resetForm(prevState: CodeCompletionSettingsState) {
|
||||
treeSitterProcessingCheckBox.isSelected = prevState.treeSitterProcessingEnabled
|
||||
gitDiffCheckBox.isSelected = prevState.gitDiffEnabled
|
||||
psiStructureAnalyzeDepthField.number = prevState.psiStructureAnalyzeDepth
|
||||
}
|
||||
|
||||
fun getFormState(): CodeCompletionSettingsState {
|
||||
|
|
@ -49,6 +63,7 @@ class CodeCompletionConfigurationForm {
|
|||
this.treeSitterProcessingEnabled = treeSitterProcessingCheckBox.isSelected
|
||||
this.gitDiffEnabled = gitDiffCheckBox.isSelected
|
||||
this.collectDependencyStructure = collectDependencyStructureBox.isSelected
|
||||
this.psiStructureAnalyzeDepth = psiStructureAnalyzeDepthField.number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ class ConfigurationSettingsState : BaseState() {
|
|||
class ChatCompletionSettingsState : BaseState() {
|
||||
var editorContextTagEnabled by property(true)
|
||||
var psiStructureEnabled by property(true)
|
||||
var psiStructureAnalyzeDepth by property(3)
|
||||
}
|
||||
|
||||
class CodeCompletionSettingsState : BaseState() {
|
||||
|
|
@ -50,4 +51,5 @@ class CodeCompletionSettingsState : BaseState() {
|
|||
var gitDiffEnabled by property(true)
|
||||
var collectDependencyStructure by property(true)
|
||||
var contextAwareEnabled by property(false)
|
||||
var psiStructureAnalyzeDepth by property(2)
|
||||
}
|
||||
|
|
@ -95,6 +95,9 @@ class PromptTextField(
|
|||
},
|
||||
onWebActionSelected = { webAction ->
|
||||
onLookupAdded(webAction)
|
||||
},
|
||||
onCodeAnalyzeSelected = { codeAnalyzeAction ->
|
||||
onLookupAdded(codeAnalyzeAction)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -322,6 +325,7 @@ class PromptTextField(
|
|||
)
|
||||
},
|
||||
onWebActionSelected = { webAction -> onLookupAdded(webAction) },
|
||||
onCodeAnalyzeSelected = { codeAnalyzeAction -> onLookupAdded(codeAnalyzeAction) },
|
||||
searchText = ""
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ee.carlrobert.codegpt.ui.textarea.lookup.DynamicLookupGroupItem
|
|||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupGroupItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.CodeAnalyzeActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.FolderActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.WebActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.files.FileActionItem
|
||||
|
|
@ -43,6 +44,7 @@ class PromptTextFieldLookupManager(
|
|||
lookupElements: Array<LookupElement>,
|
||||
onGroupSelected: (group: LookupGroupItem, searchText: String) -> Unit,
|
||||
onWebActionSelected: (WebActionItem) -> Unit,
|
||||
onCodeAnalyzeSelected: (CodeAnalyzeActionItem) -> Unit,
|
||||
searchText: String = ""
|
||||
): LookupImpl {
|
||||
val lookup = createLookup(editor, lookupElements, "")
|
||||
|
|
@ -55,6 +57,7 @@ class PromptTextFieldLookupManager(
|
|||
|
||||
when (suggestion) {
|
||||
is WebActionItem -> onWebActionSelected(suggestion)
|
||||
is CodeAnalyzeActionItem -> onCodeAnalyzeSelected(suggestion)
|
||||
is LookupGroupItem -> onGroupSelected(suggestion, searchText)
|
||||
is LookupActionItem -> onLookupAdded(suggestion)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.intellij.psi.codeStyle.NameUtil
|
|||
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagManager
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupGroupItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.CodeAnalyzeActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.WebActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.action.ImageActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.group.*
|
||||
|
|
@ -33,6 +34,7 @@ class SearchManager(
|
|||
HistoryGroupItem(),
|
||||
PersonasGroupItem(tagManager),
|
||||
DocsGroupItem(tagManager),
|
||||
CodeAnalyzeActionItem(tagManager),
|
||||
MCPGroupItem(),
|
||||
WebActionItem(tagManager),
|
||||
ImageActionItem(project, tagManager)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ internal class TagDetailsComparator : Comparator<TagDetails> {
|
|||
}
|
||||
|
||||
private fun getPriority(tag: TagDetails): Int {
|
||||
if (!tag.selected) {
|
||||
if (!tag.selected && tag !is CodeAnalyzeTagDetails) {
|
||||
return Int.MAX_VALUE
|
||||
}
|
||||
|
||||
return when (tag) {
|
||||
is CodeAnalyzeTagDetails,
|
||||
is EditorSelectionTagDetails -> 0
|
||||
|
||||
is SelectionTagDetails -> 5
|
||||
is DocumentationTagDetails,
|
||||
is PersonaTagDetails,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ object TagProcessorFactory {
|
|||
is EditorTagDetails -> EditorTagProcessor(tagDetails)
|
||||
is ImageTagDetails -> ImageTagProcessor(tagDetails)
|
||||
is EmptyTagDetails -> TagProcessor { _, _ -> }
|
||||
is CodeAnalyzeTagDetails -> TagProcessor { _, _ -> }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package ee.carlrobert.codegpt.ui.textarea.header
|
|||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.application.runUndoTransparentWriteAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.SelectionModel
|
||||
|
|
@ -17,6 +18,7 @@ import com.intellij.util.IconUtil
|
|||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.EditorNotifier
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel
|
||||
import ee.carlrobert.codegpt.ui.WrapLayout
|
||||
import ee.carlrobert.codegpt.ui.textarea.PromptTextField
|
||||
|
|
@ -107,18 +109,18 @@ class UserInputHeaderPanel(
|
|||
|
||||
val allTags = tagManager.getTags()
|
||||
|
||||
val editorVirtualFilesSet = allTags
|
||||
.filterIsInstance<EditorTagDetails>()
|
||||
val filesVirtualFilesSet = allTags
|
||||
.filterIsInstance<FileTagDetails>()
|
||||
.map { it.virtualFile }
|
||||
.toSet()
|
||||
|
||||
/**
|
||||
* Filter the tags collection to prioritize EditorTagDetails over FileTagDetails
|
||||
* Keep all tags except FileTagDetails that have a corresponding EditorTagDetails
|
||||
* Filter the tags collection to prioritize FileTagDetails over EditorTagDetails
|
||||
* Keep all tags except EditorTagDetails that have a corresponding FileTagDetails
|
||||
*/
|
||||
val tags = allTags.filter { tag ->
|
||||
if (tag is FileTagDetails) {
|
||||
!editorVirtualFilesSet.contains(tag.virtualFile)
|
||||
if (tag is EditorTagDetails) {
|
||||
!filesVirtualFilesSet.contains(tag.virtualFile)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
@ -172,6 +174,14 @@ class UserInputHeaderPanel(
|
|||
tagManager.addTag(EditorTagDetails(selectedFile))
|
||||
}
|
||||
|
||||
val psiStructureEnabled = service<ConfigurationSettings>().state
|
||||
.chatCompletionSettings
|
||||
.psiStructureEnabled
|
||||
|
||||
tagManager.addTag(
|
||||
CodeAnalyzeTagDetails().apply { selected = psiStructureEnabled }
|
||||
)
|
||||
|
||||
EditorUtil.getOpenLocalFiles(project)
|
||||
.filterNot { it == selectedFile }
|
||||
.take(INITIAL_VISIBLE_FILES)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ data class FolderTagDetails(var folder: VirtualFile) :
|
|||
|
||||
class WebTagDetails : TagDetails("Web", AllIcons.General.Web)
|
||||
|
||||
data class ImageTagDetails(val imagePath: String) :
|
||||
data class ImageTagDetails(val imagePath: String) :
|
||||
TagDetails(imagePath.substringAfterLast('/'), AllIcons.FileTypes.Image)
|
||||
|
||||
data class HistoryTagDetails(
|
||||
|
|
@ -129,4 +129,6 @@ data class HistoryTagDetails(
|
|||
val title: String,
|
||||
) : TagDetails(title, AllIcons.General.Balloon)
|
||||
|
||||
class EmptyTagDetails : TagDetails("")
|
||||
class EmptyTagDetails : TagDetails("")
|
||||
|
||||
class CodeAnalyzeTagDetails : TagDetails("Code Analyze", AllIcons.Actions.DependencyAnalyzer)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea.lookup.action
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.CodeAnalyzeTagDetails
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.EditorTagDetails
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.FileTagDetails
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagManager
|
||||
|
||||
class CodeAnalyzeActionItem(
|
||||
private val tagManager: TagManager
|
||||
) : AbstractLookupActionItem() {
|
||||
|
||||
override val displayName: String = CodeGPTBundle.get("suggestionGroupItem.codeAnalyze.displayName")
|
||||
override val icon = AllIcons.Actions.DependencyAnalyzer
|
||||
override val enabled: Boolean
|
||||
get() = tagManager.getTags().none { it is CodeAnalyzeTagDetails } &&
|
||||
tagManager.getTags().any { it is FileTagDetails || it is EditorTagDetails }
|
||||
|
||||
|
||||
override fun execute(project: Project, userInputPanel: UserInputPanel) {
|
||||
userInputPanel.addTag(CodeAnalyzeTagDetails())
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +133,8 @@ configurationConfigurable.section.codeCompletion.postProcess.title=Enable tree-s
|
|||
configurationConfigurable.section.codeCompletion.postProcess.description=If checked, the completion will be post-processed using the tree-sitter parser.
|
||||
configurationConfigurable.section.codeCompletion.gitDiff.title=Enable git diff context
|
||||
configurationConfigurable.section.codeCompletion.collectDependencyStructure.title=Enable dependency analyzer
|
||||
configurationConfigurable.section.codeCompletion.analyzeDepth.title=Code analyze depth:
|
||||
configurationConfigurable.section.codeCompletion.analyzeDepth.comment=The parameter limits the depth of the PSI structure traversal. Currently, it is implemented only for the Kotlin language.
|
||||
configurationConfigurable.section.codeCompletion.collectDependencyStructure.description=Enabling the setting allows the plugin to collect the dependency structure, which increases the accuracy of the proposed data, but consumes more tokens per request. Currently, it is implemented only for the Kotlin language.
|
||||
configurationConfigurable.section.codeCompletion.gitDiff.description=If checked, the user's most recent unstaged git diff will be included when requesting completion.
|
||||
configurationConfigurable.section.chatCompletion.title=Chat Completion
|
||||
|
|
@ -141,6 +143,8 @@ configurationConfigurable.section.chatCompletion.retryOnFailedDiffSearch.descrip
|
|||
configurationConfigurable.section.chatCompletion.editorContextTag.title=Enable automatic file tagging
|
||||
configurationConfigurable.section.chatCompletion.editorContextTag.description=If enabled, the content from open editor files will be automatically included with each message you send.
|
||||
configurationConfigurable.section.chatCompletion.psiStructure.title=Enable dependency structure analysis of attached files.
|
||||
configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.title=Code analyze depth:
|
||||
configurationConfigurable.section.chatCompletion.psiStructure.analyzeDepth.comment=The parameter limits the depth of the PSI structure traversal. Currently, it is implemented only for the Kotlin language.
|
||||
configurationConfigurable.section.chatCompletion.psiStructure.description=If enabled, the class structure that is present in the imports of the attached files will be added in the context of the dialog. A structure refers to the source code in files that include constructors, fields, and methods, with all modifiers, arguments, and return types, but without an implementation. The implementation of dependencies is intentionally excluded in order to find a balance between a high-quality chat context and saving tokens.
|
||||
settingsConfigurable.service.llama.topK.label=Top K:
|
||||
settingsConfigurable.service.llama.topK.comment=Limit the next token selection to the K most probable tokens (default: 40)
|
||||
|
|
@ -314,6 +318,7 @@ suggestionGroupItem.history.displayName=History
|
|||
suggestionGroupItem.docs.displayName=Docs
|
||||
suggestionGroupItem.git.displayName=Git
|
||||
suggestionGroupItem.mcp.displayName=MCP (soon)
|
||||
suggestionGroupItem.codeAnalyze.displayName=Code Analyze
|
||||
suggestionActionItem.attachImage.displayName=Image
|
||||
suggestionActionItem.attachImage.description=Select an image file to attach
|
||||
suggestionActionItem.webSearch.displayName=Web
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue