refactor: rewrite method lookup listener and improve error handling

This commit is contained in:
Carl-Robert Linnupuu 2024-11-14 13:56:07 +00:00
parent 808fdfaf7e
commit 755791aea8
3 changed files with 104 additions and 103 deletions

View file

@ -1,102 +0,0 @@
package ee.carlrobert.codegpt.completions;
import static ee.carlrobert.codegpt.CodeGPTKeys.IS_PROMPT_TEXT_FIELD_DOCUMENT;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.LookupManagerListener;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtilCore;
import ee.carlrobert.codegpt.Icons;
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
import java.util.List;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
public class MethodNameLookupListener implements LookupManagerListener {
private static final Logger LOG = Logger.getInstance(MethodNameLookupListener.class);
@Override
public void activeLookupChanged(@Nullable Lookup oldLookup, @Nullable Lookup newLookup) {
if (!(newLookup instanceof LookupImpl lookup)) {
return;
}
Boolean isPromptTextFieldDocument = IS_PROMPT_TEXT_FIELD_DOCUMENT.get(
lookup.getEditor().getDocument());
if ((isPromptTextFieldDocument != null && isPromptTextFieldDocument)
|| !ConfigurationSettings.getState().getMethodNameGenerationEnabled()
|| !CompletionRequestService.isRequestAllowed()) {
return;
}
var application = ApplicationManager.getApplication();
Optional.ofNullable(lookup.getPsiElement())
.map(PsiElement::getContext)
.ifPresent(context ->
application.runReadAction(() -> {
var type = PsiUtilCore.getElementType(context);
if (PSIMethodMapping.contains(type)) {
var selection = context.getText();
application.executeOnPooledThread(() ->
addCompletionLookupValues(lookup, application, selection));
}
}));
}
private void addCompletionLookupValues(
LookupImpl lookup,
Application application,
String prompt) {
try {
var response = CompletionRequestService.getInstance()
.getLookupCompletion(new LookupCompletionParameters(prompt));
if (!response.isEmpty()) {
for (var value : response.split(",")) {
application.invokeLater(() -> application.runReadAction(() -> {
lookup.addItem(
LookupElementBuilder.create(value.trim()).withIcon(Icons.Sparkle),
PrefixMatcher.ALWAYS_TRUE);
lookup.refreshUi(true, true);
}));
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to add completion lookup values", e);
}
}
private enum PSIMethodMapping {
GO(List.of("FILE", "METHOD_DECLARATION|FUNCTION_DECLARATION")),
JAVA(List.of("java.FILE", "METHOD")),
PY(List.of("FILE", "Py:FUNCTION_DECLARATION")),
JAVASCRIPT(List.of("JS:FUNCTION_DECLARATION", "JS:TYPESCRIPT_FUNCTION")),
CS(List.of("FILE", "DUMMY_TYPE_DECLARATION", "DUMMY_BLOCK")),
PHP(List.of("FILE", "Class method|function|Function")),
KOTLIN(List.of("FUN")),
DEFAULT(List.of("FILE", "METHOD_DECLARATION"));
private final List<String> types;
PSIMethodMapping(List<String> codes) {
this.types = codes;
}
public static boolean contains(IElementType type) {
LOG.info("Trying to find method mapping for type: " + type.toString());
for (var value : PSIMethodMapping.values()) {
if (value.types.contains(type.toString())) {
return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,103 @@
package ee.carlrobert.codegpt.completions
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.lookup.LookupManagerListener
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiUtilCore
import ee.carlrobert.codegpt.CodeGPTKeys.IS_PROMPT_TEXT_FIELD_DOCUMENT
import ee.carlrobert.codegpt.Icons
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.ui.OverlayUtil
import ee.carlrobert.llm.client.codegpt.response.CodeGPTException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class MethodNameCompletionLookupListener : LookupManagerListener {
companion object {
private val logger = thisLogger()
}
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
if (newLookup !is LookupImpl) {
return
}
val isPromptTextFieldDocument =
IS_PROMPT_TEXT_FIELD_DOCUMENT[newLookup.editor.document] ?: false
val featureEnabled = service<ConfigurationSettings>().state.methodNameGenerationEnabled
if (isPromptTextFieldDocument || !featureEnabled || !CompletionRequestService.isRequestAllowed()) {
return
}
newLookup.psiElement?.context?.let { context ->
val elementType = PsiUtilCore.getElementType(context)
if (PSIMethodMapping.contains(elementType)) {
val selection = runReadAction { context.text }
scope.launch {
try {
val lookupCompletion = service<CompletionRequestService>()
.getLookupCompletion(LookupCompletionParameters(selection))
if (lookupCompletion.isNotEmpty()) {
addCompletionLookupValues(newLookup, lookupCompletion)
}
} catch (ex: CodeGPTException) {
OverlayUtil.showNotification(ex.detail, NotificationType.ERROR)
} catch (ex: Exception) {
logger.error(
"Something went wrong while requesting completion lookup values.",
ex
)
}
}
}
}
}
private fun addCompletionLookupValues(lookup: LookupImpl, response: String) {
val values = response.split(",".toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()
for (value in values) {
runInEdt {
lookup.addItem(
LookupElementBuilder
.create(value.trim { it <= ' ' })
.withIcon(Icons.DefaultSmall),
PrefixMatcher.ALWAYS_TRUE
)
lookup.refreshUi(true, true)
}
}
}
private enum class PSIMethodMapping(val types: List<String>) {
GO(listOf("FILE", "METHOD_DECLARATION|FUNCTION_DECLARATION")),
JAVA(listOf("java.FILE", "METHOD")),
PY(listOf("FILE", "Py:FUNCTION_DECLARATION")),
JAVASCRIPT(listOf("JS:FUNCTION_DECLARATION", "JS:TYPESCRIPT_FUNCTION")),
CS(listOf("FILE", "DUMMY_TYPE_DECLARATION", "DUMMY_BLOCK")),
PHP(listOf("FILE", "Class method|function|Function")),
KOTLIN(listOf("FUN")),
DEFAULT(listOf("FILE", "METHOD_DECLARATION"));
companion object {
fun contains(type: IElementType): Boolean {
return entries.any { it.types.contains(type.toString()) }
}
}
}
}

View file

@ -16,7 +16,7 @@
<projectListeners>
<listener topic="com.intellij.codeInsight.lookup.LookupManagerListener"
class="ee.carlrobert.codegpt.completions.MethodNameLookupListener"/>
class="ee.carlrobert.codegpt.completions.MethodNameCompletionLookupListener"/>
<listener topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"
class="ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowListener"/>
</projectListeners>