Skip to content

Commit 4a62471

Browse files
committed
feat: replace custom suggestions UI logic with editor lookup API
1 parent 3de3c65 commit 4a62471

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1224
-1245
lines changed

src/main/java/ee/carlrobert/codegpt/Icons.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public final class Icons {
1818
public static final Icon Google = IconLoader.getIcon("/icons/google.svg", Icons.class);
1919
public static final Icon Llama = IconLoader.getIcon("/icons/llama.svg", Icons.class);
2020
public static final Icon OpenAI = IconLoader.getIcon("/icons/openai.svg", Icons.class);
21+
public static final Icon MCP = IconLoader.getIcon("/icons/mcp.svg", Icons.class);
2122
public static final Icon Meta = IconLoader.getIcon("/icons/meta.svg", Icons.class);
2223
public static final Icon Mistral = IconLoader.getIcon("/icons/mistral.svg", Icons.class);
2324
public static final Icon Send = IconLoader.getIcon("/icons/send.svg", Icons.class);

src/main/java/ee/carlrobert/codegpt/ReferencedFile.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
package ee.carlrobert.codegpt;
22

33
import com.intellij.openapi.fileEditor.FileDocumentManager;
4-
import com.intellij.openapi.vfs.VfsUtilCore;
54
import com.intellij.openapi.vfs.VirtualFile;
65
import ee.carlrobert.codegpt.util.file.FileUtil;
76
import java.io.File;
8-
import java.io.IOException;
9-
import java.nio.file.Files;
10-
import java.nio.file.Paths;
117
import java.util.Objects;
128
import java.util.regex.Matcher;
139
import java.util.regex.Pattern;
1410

15-
public record ReferencedFile(String fileName, String filePath, String fileContent) {
11+
public record ReferencedFile(String fileName, String filePath, String fileContent,
12+
boolean directory) {
13+
14+
public ReferencedFile(String fileName, String filePath, String fileContent) {
15+
this(fileName, filePath, fileContent, false);
16+
}
17+
18+
public ReferencedFile(String fileName, String filePath, String fileContent, boolean directory) {
19+
this.fileName = fileName;
20+
this.filePath = filePath;
21+
this.fileContent = fileContent;
22+
this.directory = directory;
23+
}
1624

1725
public static ReferencedFile from(File file) {
1826
return new ReferencedFile(
1927
file.getName(),
2028
file.getPath(),
21-
FileUtil.readContent(file)
29+
FileUtil.readContent(file),
30+
file.isDirectory()
2231
);
2332
}
2433

2534
public static ReferencedFile from(VirtualFile virtualFile) {
2635
return new ReferencedFile(
2736
virtualFile.getName(),
2837
virtualFile.getPath(),
29-
getVirtualFileContent(virtualFile)
38+
getVirtualFileContent(virtualFile),
39+
virtualFile.isDirectory()
3040
);
3141
}
3242

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
4141
import ee.carlrobert.codegpt.ui.textarea.header.tag.EditorTagDetails;
4242
import ee.carlrobert.codegpt.ui.textarea.header.tag.FileTagDetails;
43+
import ee.carlrobert.codegpt.ui.textarea.header.tag.FolderTagDetails;
4344
import ee.carlrobert.codegpt.ui.textarea.header.tag.GitCommitTagDetails;
4445
import ee.carlrobert.codegpt.ui.textarea.header.tag.PersonaTagDetails;
4546
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails;
@@ -195,6 +196,8 @@ private VirtualFile getVirtualFile(TagDetails tag) {
195196
virtualFile = ((FileTagDetails) tag).getVirtualFile();
196197
} else if (tag instanceof EditorTagDetails) {
197198
virtualFile = ((EditorTagDetails) tag).getVirtualFile();
199+
} else if (tag instanceof FolderTagDetails) {
200+
virtualFile = ((FolderTagDetails) tag).getFolder();
198201
}
199202

200203
}

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/SelectedFilesAccordion.java

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@
33
import static java.lang.String.format;
44

55
import com.intellij.icons.AllIcons.General;
6-
import com.intellij.openapi.fileEditor.FileEditorManager;
7-
import com.intellij.openapi.project.Project;
8-
import com.intellij.openapi.vfs.VirtualFile;
96
import com.intellij.ui.components.ActionLink;
107
import com.intellij.util.ui.JBUI;
118
import java.awt.BorderLayout;
129
import java.awt.event.ItemEvent;
13-
import java.nio.file.Paths;
1410
import java.util.List;
15-
import java.util.Objects;
1611
import javax.swing.Box;
1712
import javax.swing.BoxLayout;
1813
import javax.swing.JPanel;
@@ -22,38 +17,25 @@
2217

2318
public class SelectedFilesAccordion extends JPanel {
2419

25-
public SelectedFilesAccordion(
26-
@NotNull Project project,
27-
@NotNull List<VirtualFile> referencedFiles) {
20+
public SelectedFilesAccordion(@NotNull List<ActionLink> links) {
2821
super(new BorderLayout());
2922
setOpaque(false);
3023

31-
var contentPanel = createContentPanel(project, referencedFiles);
32-
add(createToggleButton(contentPanel, referencedFiles.size()), BorderLayout.NORTH);
24+
var contentPanel = createContentPanel(links);
25+
add(createToggleButton(contentPanel, links.size()), BorderLayout.NORTH);
3326
add(contentPanel, BorderLayout.CENTER);
3427
}
3528

36-
private JPanel createContentPanel(Project project, List<VirtualFile> referencedFiles) {
29+
private JPanel createContentPanel(List<ActionLink> links) {
3730
var panel = new JPanel();
3831
panel.setOpaque(false);
3932
panel.setVisible(true);
4033
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
4134
panel.setBorder(JBUI.Borders.empty(4, 0));
42-
referencedFiles.stream()
43-
.map(virtualFile -> {
44-
var actionLink = new ActionLink(
45-
Paths.get(virtualFile.getPath()).getFileName().toString(),
46-
event -> {
47-
FileEditorManager.getInstance(project)
48-
.openFile(Objects.requireNonNull(virtualFile), true);
49-
});
50-
actionLink.setIcon(virtualFile.getFileType().getIcon());
51-
return actionLink;
52-
})
53-
.forEach(link -> {
54-
panel.add(link);
55-
panel.add(Box.createVerticalStrut(4));
56-
});
35+
links.forEach(link -> {
36+
panel.add(link);
37+
panel.add(Box.createVerticalStrut(4));
38+
});
5739
return panel;
5840
}
5941

src/main/kotlin/ee/carlrobert/codegpt/CodeGPTLookupListener.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import com.intellij.codeInsight.lookup.LookupEvent
55
import com.intellij.codeInsight.lookup.LookupListener
66
import com.intellij.codeInsight.lookup.LookupManagerListener
77
import com.intellij.codeInsight.lookup.impl.LookupImpl
8+
import com.intellij.notification.NotificationType
89
import com.intellij.openapi.application.ApplicationManager
910
import com.intellij.openapi.application.runReadAction
1011
import com.intellij.openapi.components.service
12+
import com.intellij.openapi.editor.EditorKind
1113
import ee.carlrobert.codegpt.predictions.PredictionService
1214
import ee.carlrobert.codegpt.settings.GeneralSettings
1315
import ee.carlrobert.codegpt.settings.service.ServiceType
1416
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
17+
import ee.carlrobert.codegpt.ui.OverlayUtil
1518

1619
class CodeGPTLookupListener : LookupManagerListener {
1720
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
@@ -34,10 +37,20 @@ class CodeGPTLookupListener : LookupManagerListener {
3437
val editor = newLookup.editor
3538
if (GeneralSettings.getSelectedService() != ServiceType.CODEGPT
3639
|| !service<CodeGPTServiceSettings>().state.nextEditsEnabled
40+
|| editor.editorKind != EditorKind.MAIN_EDITOR
3741
) {
3842
return
3943
}
4044

45+
val settings = service<CodeGPTServiceSettings>().state
46+
if (settings.codeCompletionSettings.codeCompletionsEnabled) {
47+
settings.codeCompletionSettings.codeCompletionsEnabled = false
48+
OverlayUtil.showNotification(
49+
"Code completions and multi-line edits cannot be active simultaneously.",
50+
NotificationType.WARNING
51+
)
52+
}
53+
4154
ApplicationManager.getApplication().executeOnPooledThread {
4255
service<PredictionService>().displayInlineDiff(editor)
4356
}

src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.intellij.openapi.diagnostic.thisLogger
1212
import com.intellij.openapi.project.Project
1313
import com.intellij.openapi.util.TextRange
1414
import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION
15-
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
15+
import ee.carlrobert.codegpt.predictions.PredictionService
1616
import ee.carlrobert.codegpt.settings.GeneralSettings
1717
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
1818
import ee.carlrobert.codegpt.settings.service.ServiceType
@@ -56,8 +56,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
5656

5757
override suspend fun getSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSuggestion {
5858
val codegptSettings = service<CodeGPTServiceSettings>().state
59-
if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT && codegptSettings.nextEditsEnabled
60-
) {
59+
if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT && codegptSettings.nextEditsEnabled) {
6160
if (codegptSettings.codeCompletionSettings.codeCompletionsEnabled) {
6261
codegptSettings.codeCompletionSettings.codeCompletionsEnabled = false
6362
OverlayUtil.showNotification(
@@ -81,7 +80,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
8180
val project = request.editor.project ?: return
8281
try {
8382
CompletionProgressNotifier.update(project, true)
84-
project.service<GrpcClientService>().getNextEdit(request.editor)
83+
project.service<PredictionService>().displayInlineDiff(request.editor)
8584
} catch (ex: Exception) {
8685
logger.error("Error communicating with server: ${ex.message}")
8786
}
@@ -177,7 +176,8 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
177176
REMAINING_EDITOR_COMPLETION.get(event.toRequest()?.editor)?.isNotEmpty() ?: false
178177

179178
if (!codeCompletionsEnabled) {
180-
return selectedService == ServiceType.CODEGPT
179+
return event is InlineCompletionEvent.DocumentChange
180+
&& selectedService == ServiceType.CODEGPT
181181
&& service<CodeGPTServiceSettings>().state.nextEditsEnabled
182182
&& !hasActiveCompletion
183183
}

src/main/kotlin/ee/carlrobert/codegpt/completions/factory/CodeGPTRequestFactory.kt

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ package ee.carlrobert.codegpt.completions.factory
22

33
import com.intellij.openapi.application.ApplicationInfo
44
import com.intellij.openapi.components.service
5+
import com.intellij.openapi.vfs.LocalFileSystem
6+
import com.intellij.openapi.vfs.VirtualFile
57
import ee.carlrobert.codegpt.CodeGPTPlugin
68
import ee.carlrobert.codegpt.completions.BaseRequestFactory
79
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
810
import ee.carlrobert.codegpt.completions.factory.OpenAIRequestFactory.Companion.buildOpenAIMessages
911
import ee.carlrobert.codegpt.psistructure.ClassStructureSerializer
1012
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
1113
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
12-
import ee.carlrobert.llm.client.codegpt.request.chat.AdditionalRequestContext
13-
import ee.carlrobert.llm.client.codegpt.request.chat.ChatCompletionRequest
14-
import ee.carlrobert.llm.client.codegpt.request.chat.ContextFile
15-
import ee.carlrobert.llm.client.codegpt.request.chat.DocumentationDetails
16-
import ee.carlrobert.llm.client.codegpt.request.chat.Metadata
14+
import ee.carlrobert.codegpt.util.file.FileUtil
15+
import ee.carlrobert.llm.client.codegpt.request.chat.*
1716
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage
1817

19-
class CodeGPTRequestFactory(private val classStructureSerializer: ClassStructureSerializer) : BaseRequestFactory() {
18+
class CodeGPTRequestFactory(private val classStructureSerializer: ClassStructureSerializer) :
19+
BaseRequestFactory() {
2020

2121
override fun createChatRequest(params: ChatCompletionParameters): ChatCompletionRequest {
2222
val model = service<CodeGPTServiceSettings>().state.chatCompletionSettings.model
@@ -47,17 +47,30 @@ class CodeGPTRequestFactory(private val classStructureSerializer: ClassStructure
4747
requestBuilder.setWebSearchIncluded(true)
4848
}
4949
params.message.documentationDetails?.let {
50-
requestBuilder.setDocumentationDetails(
51-
DocumentationDetails(it.name, it.url)
52-
)
50+
requestBuilder.setDocumentationDetails(DocumentationDetails(it.name, it.url))
5351
}
5452

55-
val contextFiles = params.referencedFiles?.map { file ->
56-
ContextFile(file.fileName(), file.fileContent())
57-
}.orEmpty()
53+
val contextFiles = params.referencedFiles
54+
?.mapNotNull { file ->
55+
LocalFileSystem.getInstance().findFileByPath(file.filePath)?.let {
56+
if (it.isDirectory) {
57+
val children = mutableListOf<ContextFile>()
58+
processFolder(it, children)
59+
children
60+
} else {
61+
listOf(ContextFile(file.fileName(), file.fileContent()))
62+
}
63+
}
64+
}
65+
?.flatten()
66+
.orEmpty()
67+
5868

5969
val psiContext = params.psiStructure?.map { classStructure ->
60-
ContextFile(classStructure.virtualFile.name, classStructureSerializer.serialize(classStructure))
70+
ContextFile(
71+
classStructure.virtualFile.name,
72+
classStructureSerializer.serialize(classStructure)
73+
)
6174
}.orEmpty()
6275

6376
val contextFilesWithPsi = contextFiles + psiContext
@@ -68,6 +81,15 @@ class CodeGPTRequestFactory(private val classStructureSerializer: ClassStructure
6881
return requestBuilder.build()
6982
}
7083

84+
private fun processFolder(folder: VirtualFile, contextFiles: MutableList<ContextFile>) {
85+
folder.children.forEach { child ->
86+
when {
87+
child.isDirectory -> processFolder(child, contextFiles)
88+
else -> contextFiles.add(ContextFile(child.name, FileUtil.readContent(child)))
89+
}
90+
}
91+
}
92+
7193
override fun createBasicCompletionRequest(
7294
systemPrompt: String,
7395
userPrompt: String,

src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/UserMessagePanel.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import com.intellij.icons.AllIcons.Actions
55
import com.intellij.openapi.Disposable
66
import com.intellij.openapi.actionSystem.AnAction
77
import com.intellij.openapi.actionSystem.AnActionEvent
8+
import com.intellij.openapi.application.runInEdt
9+
import com.intellij.openapi.fileEditor.FileEditorManager
810
import com.intellij.openapi.project.Project
911
import com.intellij.openapi.vfs.LocalFileSystem
1012
import com.intellij.ui.ColorUtil
1113
import com.intellij.ui.JBColor
1214
import com.intellij.ui.RoundedIcon
15+
import com.intellij.ui.components.ActionLink
1316
import com.intellij.ui.components.JBLabel
17+
import com.intellij.util.application
1418
import com.intellij.util.ui.JBFont
1519
import com.intellij.util.ui.JBUI
1620
import com.intellij.util.ui.components.BorderLayoutPanel
@@ -23,11 +27,9 @@ import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody
2327
import ee.carlrobert.codegpt.toolwindow.chat.ui.ImageAccordion
2428
import ee.carlrobert.codegpt.toolwindow.chat.ui.SelectedFilesAccordion
2529
import ee.carlrobert.codegpt.ui.IconActionButton
26-
import kotlinx.coroutines.CoroutineScope
27-
import kotlinx.coroutines.Dispatchers
28-
import kotlinx.coroutines.launch
29-
import kotlinx.coroutines.withContext
3030
import java.awt.Image
31+
import java.awt.event.ActionEvent
32+
import java.awt.event.ActionListener
3133
import java.io.IOException
3234
import java.nio.file.Files
3335
import java.nio.file.Paths
@@ -193,12 +195,25 @@ class UserMessagePanel(
193195
}
194196

195197
if (referencedFilePaths.isNotEmpty()) {
196-
CoroutineScope(Dispatchers.IO).launch {
197-
val referencedFiles = referencedFilePaths.mapNotNull {
198-
LocalFileSystem.getInstance().findFileByPath(it)
199-
}
200-
withContext(Dispatchers.Main) {
201-
additionalContextPanel.add(SelectedFilesAccordion(project, referencedFiles))
198+
application.executeOnPooledThread {
199+
val links = referencedFilePaths
200+
.mapNotNull {
201+
LocalFileSystem.getInstance().findFileByPath(it)
202+
}
203+
.map {
204+
val actionLink = ActionLink(
205+
Paths.get(it.path).fileName.toString(),
206+
ActionListener { _: ActionEvent ->
207+
FileEditorManager.getInstance(project)
208+
.openFile(Objects.requireNonNull(it), true)
209+
})
210+
actionLink.icon =
211+
if (it.isDirectory) AllIcons.Nodes.Folder else it.fileType.icon
212+
actionLink
213+
}
214+
.toList()
215+
runInEdt {
216+
additionalContextPanel.add(SelectedFilesAccordion(links))
202217
}
203218
}
204219
}

0 commit comments

Comments
 (0)