Skip to content

Commit 3011dba

Browse files
committed
feat: quick link navigation
1 parent 2a0a9d9 commit 3011dba

31 files changed

+586
-158
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
import com.intellij.openapi.Disposable;
66
import com.intellij.openapi.application.ApplicationManager;
7+
import com.intellij.openapi.application.ModalityState;
8+
import com.intellij.openapi.application.ReadAction;
79
import com.intellij.openapi.diagnostic.Logger;
810
import com.intellij.openapi.editor.SelectionModel;
911
import com.intellij.openapi.project.Project;
1012
import com.intellij.openapi.vfs.VirtualFile;
1113
import com.intellij.ui.JBColor;
14+
import com.intellij.util.concurrency.AppExecutorUtil;
1215
import com.intellij.util.ui.JBUI;
1316
import ee.carlrobert.codegpt.CodeGPTKeys;
1417
import ee.carlrobert.codegpt.ReferencedFile;
@@ -178,6 +181,7 @@ private ChatCompletionParameters getCallParameters(
178181
.referencedFiles(getReferencedFiles(selectedTags))
179182
.history(getHistory(getSelectedTags()))
180183
.psiStructure(psiStructure)
184+
.project(project)
181185
.chatMode(userInputPanel.getChatMode());
182186

183187
findTagOfType(selectedTags, PersonaTagDetails.class)
@@ -286,8 +290,15 @@ public void clearAllTags() {
286290

287291
public void includeFiles(List<VirtualFile> referencedFiles) {
288292
userInputPanel.includeFiles(referencedFiles);
289-
totalTokensPanel.updateReferencedFilesTokens(
290-
referencedFiles.stream().map(it -> ReferencedFile.from(it).fileContent()).toList());
293+
ReadAction.nonBlocking(() ->
294+
referencedFiles.stream()
295+
.map(it -> ReferencedFile.from(it).fileContent())
296+
.toList()
297+
)
298+
.inSmartMode(project)
299+
.expireWith(project)
300+
.finishOnUiThread(ModalityState.any(), totalTokensPanel::updateReferencedFilesTokens)
301+
.submit(AppExecutorUtil.getAppExecutorService());
291302
}
292303

293304
private boolean hasReferencedFilePaths(Message message) {
@@ -498,6 +509,7 @@ private UserMessagePanel getUserMessagePanel(Message message) {
498509
userMessagePanel.addReloadAction(() -> reloadMessage(
499510
ChatCompletionParameters.builder(conversation, message)
500511
.conversationType(ConversationType.DEFAULT)
512+
.project(project)
501513
.chatMode(userInputPanel.getChatMode())
502514
.build(),
503515
userMessagePanel));

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@
1313
import com.intellij.openapi.actionSystem.AnActionEvent;
1414
import com.intellij.openapi.actionSystem.DefaultActionGroup;
1515
import com.intellij.openapi.application.ApplicationManager;
16+
import com.intellij.openapi.application.ModalityState;
17+
import com.intellij.openapi.application.ReadAction;
1618
import com.intellij.openapi.diagnostic.Logger;
1719
import com.intellij.openapi.fileEditor.FileEditorManager;
1820
import com.intellij.openapi.options.ShowSettingsUtil;
1921
import com.intellij.openapi.project.Project;
2022
import com.intellij.openapi.ui.VerticalFlowLayout;
23+
import com.intellij.openapi.util.Disposer;
2124
import com.intellij.openapi.util.io.FileUtil;
2225
import com.intellij.openapi.vfs.LocalFileSystem;
2326
import com.intellij.openapi.vfs.VirtualFile;
2427
import com.intellij.ui.AnimatedIcon;
2528
import com.intellij.ui.PopupHandler;
2629
import com.intellij.ui.components.JBLabel;
30+
import com.intellij.util.concurrency.AppExecutorUtil;
2731
import com.intellij.util.ui.JBUI;
2832
import com.intellij.util.ui.components.BorderLayoutPanel;
2933
import ee.carlrobert.codegpt.CodeGPTBundle;
@@ -58,6 +62,7 @@
5862
import ee.carlrobert.codegpt.ui.ThoughtProcessPanel;
5963
import ee.carlrobert.codegpt.ui.UIUtil;
6064
import ee.carlrobert.codegpt.util.EditorUtil;
65+
import ee.carlrobert.codegpt.util.MarkdownUtil;
6166
import java.awt.BorderLayout;
6267
import java.util.Objects;
6368
import java.util.stream.Stream;

src/main/java/ee/carlrobert/codegpt/ui/UIUtil.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
44

55
import com.intellij.ide.BrowserUtil;
6+
import com.intellij.openapi.application.ApplicationManager;
7+
import com.intellij.openapi.diagnostic.Logger;
68
import com.intellij.openapi.roots.ui.componentsList.components.ScrollablePanel;
79
import com.intellij.openapi.ui.panel.ComponentPanelBuilder;
810
import com.intellij.ui.JBColor;
911
import com.intellij.ui.ScrollPaneFactory;
1012
import com.intellij.ui.components.JBRadioButton;
1113
import com.intellij.ui.components.JBTextArea;
14+
import com.intellij.util.ui.HTMLEditorKitBuilder;
1215
import com.intellij.util.ui.JBUI;
1316
import com.intellij.util.ui.UI;
1417
import ee.carlrobert.codegpt.CodeGPTBundle;
1518
import ee.carlrobert.codegpt.toolwindow.chat.ui.SmartScroller;
19+
import ee.carlrobert.codegpt.util.PsiLinkNavigator;
1620
import java.awt.BorderLayout;
1721
import java.awt.CardLayout;
1822
import java.awt.Component;
@@ -43,6 +47,8 @@
4347

4448
public class UIUtil {
4549

50+
private static final Logger LOG = Logger.getInstance(UIUtil.class);
51+
4652
public static JTextPane createTextPane(String text) {
4753
return createTextPane(text, true);
4854
}
@@ -54,6 +60,7 @@ public static JTextPane createTextPane(String text, boolean opaque) {
5460
public static JTextPane createTextPane(String text, boolean opaque, HyperlinkListener listener) {
5561
var textPane = new JTextPane();
5662
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
63+
textPane.setEditorKit(HTMLEditorKitBuilder.simple());
5764
textPane.addHyperlinkListener(listener);
5865
textPane.setContentType("text/html");
5966
textPane.setEditable(false);
@@ -103,12 +110,23 @@ public static JPanel createPanel(JComponent component, String label, boolean res
103110
}
104111

105112
public static void handleHyperlinkClicked(HyperlinkEvent event) {
113+
if (!ACTIVATED.equals(event.getEventType())) {
114+
return;
115+
}
116+
117+
String desc = event.getDescription();
118+
if (desc != null && PsiLinkNavigator.isValidNavigationLink(desc)) {
119+
ApplicationManager.getApplication()
120+
.executeOnPooledThread(() -> PsiLinkNavigator.handle(desc));
121+
return;
122+
}
123+
106124
var url = event.getURL();
107-
if (ACTIVATED.equals(event.getEventType()) && url != null) {
125+
if (url != null) {
108126
try {
109127
BrowserUtil.browse(url.toURI());
110128
} catch (URISyntaxException e) {
111-
throw new RuntimeException(e);
129+
LOG.warn("Failed to browse URL: " + url, e);
112130
}
113131
}
114132
}
@@ -119,7 +137,6 @@ public static void addShiftEnterInputMap(JTextArea textArea, AbstractAction onSu
119137
textArea.getActionMap().put("text-submit", onSubmit);
120138
}
121139

122-
123140
public static JPanel createRadioButtonsPanel(List<JBRadioButton> radioButtons) {
124141
var buttonGroup = new ButtonGroup();
125142
var radioPanel = new JPanel();

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,20 @@ class CodeCompletionEventListener(
116116
}
117117

118118
if (firstLineSent.get() && firstLine != null) {
119-
val remainingContent = finalResult.removePrefix(firstLine!!).toString()
119+
val first = firstLine ?: return
120+
val remainingContent = finalResult.removePrefix(first).toString()
120121
if (remainingContent.trim().isEmpty()) {
121122
return
122123
}
123124

124-
val parsedContent = parseOutput(firstLine + remainingContent)
125+
val parsedContent = parseOutput(first + remainingContent)
125126
if (parsedContent.isNotEmpty()) {
126127
cache?.setCache(prefix, suffix, parsedContent)
127128

128129
CodeGPTKeys.REMAINING_CODE_COMPLETION.set(
129130
editor,
130131
PartialCodeCompletionResponse.newBuilder()
131-
.setPartialCompletion(parsedContent.removePrefix(firstLine ?: ""))
132+
.setPartialCompletion(parsedContent.removePrefix(first))
132133
.build()
133134
)
134135
}
@@ -200,4 +201,4 @@ class CodeCompletionEventListener(
200201
.parse(prefix, suffix, input)
201202
.trimEnd()
202203
}
203-
}
204+
}

src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionParameters.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ee.carlrobert.codegpt.completions
22

3+
import com.intellij.openapi.project.Project
34
import com.intellij.openapi.vfs.VirtualFile
45
import ee.carlrobert.codegpt.ReferencedFile
56
import ee.carlrobert.codegpt.conversations.Conversation
@@ -26,6 +27,7 @@ class ChatCompletionParameters private constructor(
2627
var referencedFiles: List<ReferencedFile>?,
2728
var personaDetails: PersonaDetails?,
2829
var psiStructure: Set<ClassStructure>?,
30+
var project: Project?,
2931
var chatMode: ChatMode = ChatMode.ASK,
3032
var featureType: FeatureType = FeatureType.CHAT
3133
) : CompletionParameters {
@@ -39,6 +41,7 @@ class ChatCompletionParameters private constructor(
3941
referencedFiles(this@ChatCompletionParameters.referencedFiles)
4042
personaDetails(this@ChatCompletionParameters.personaDetails)
4143
psiStructure(this@ChatCompletionParameters.psiStructure)
44+
project(this@ChatCompletionParameters.project)
4245
chatMode(this@ChatCompletionParameters.chatMode)
4346
featureType(this@ChatCompletionParameters.featureType)
4447
}
@@ -54,6 +57,7 @@ class ChatCompletionParameters private constructor(
5457
private var personaDetails: PersonaDetails? = null
5558
private var psiStructure: Set<ClassStructure>? = null
5659
private var gitDiff: String = ""
60+
private var project: Project? = null
5761
private var chatMode: ChatMode = ChatMode.ASK
5862
private var featureType: FeatureType = FeatureType.CHAT
5963

@@ -83,6 +87,8 @@ class ChatCompletionParameters private constructor(
8387

8488
fun psiStructure(psiStructure: Set<ClassStructure>?) = apply { this.psiStructure = psiStructure }
8589

90+
fun project(project: Project?) = apply { this.project = project }
91+
8692
fun chatMode(chatMode: ChatMode) = apply { this.chatMode = chatMode }
8793

8894
fun featureType(featureType: FeatureType) = apply { this.featureType = featureType }
@@ -99,6 +105,7 @@ class ChatCompletionParameters private constructor(
99105
referencedFiles,
100106
personaDetails,
101107
psiStructure,
108+
project,
102109
chatMode,
103110
featureType
104111
)

src/main/kotlin/ee/carlrobert/codegpt/completions/CompletionRequestUtil.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ object CompletionRequestUtil {
4343
fun getPromptWithContext(
4444
referencedFiles: List<ReferencedFile>,
4545
userPrompt: String?,
46-
psiStructure: Set<ClassStructure>?
46+
psiStructure: Set<ClassStructure>?,
4747
): String {
4848
val includedFilesSettings = service<IncludedFilesSettings>().state
4949
val repeatableContext = includedFilesSettings.repeatableContext
@@ -77,7 +77,10 @@ object CompletionRequestUtil {
7777
}
7878

7979
return includedFilesSettings.promptTemplate
80-
.replace("{REPEATABLE_CONTEXT}", fileContext + structureContext.orEmpty())
80+
.replace("{REPEATABLE_CONTEXT}", buildString {
81+
append(fileContext)
82+
append(structureContext.orEmpty())
83+
})
8184
.replace("{QUESTION}", userPrompt!!)
8285
}
83-
}
86+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.intellij.openapi.components.service
44
import ee.carlrobert.codegpt.completions.BaseRequestFactory
55
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
66
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
7-
import ee.carlrobert.codegpt.settings.models.ModelSettings
87
import ee.carlrobert.codegpt.settings.prompts.FilteredPromptsService
98
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
109
import ee.carlrobert.codegpt.settings.service.FeatureType
@@ -22,7 +21,8 @@ class ClaudeRequestFactory : BaseRequestFactory() {
2221

2322
val selectedPersona = service<PromptsSettings>().state.personas.selectedPersona
2423
if (!selectedPersona.disabled) {
25-
system = service<FilteredPromptsService>().getFilteredPersonaPrompt(params.chatMode)
24+
val base = service<FilteredPromptsService>().getFilteredPersonaPrompt(params.chatMode)
25+
system = service<FilteredPromptsService>().applyClickableLinks(base)
2626
}
2727

2828
messages = params.conversation.messages

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ import ee.carlrobert.codegpt.conversations.ConversationsState
1010
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
1111
import ee.carlrobert.codegpt.settings.prompts.FilteredPromptsService
1212
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
13-
import ee.carlrobert.codegpt.settings.service.google.GoogleSettings
1413
import ee.carlrobert.codegpt.settings.service.FeatureType
1514
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
16-
import ee.carlrobert.codegpt.settings.models.ModelSettings
1715
import ee.carlrobert.codegpt.util.file.FileUtil
1816
import ee.carlrobert.llm.client.google.completion.GoogleCompletionContent
1917
import ee.carlrobert.llm.client.google.completion.GoogleCompletionRequest
@@ -130,15 +128,16 @@ class GoogleRequestFactory : BaseRequestFactory() {
130128
messages.add(GoogleCompletionContent("model", listOf(prevMessage.response)))
131129
}
132130

133-
if (params.imageDetails != null) {
131+
val imageDetails = params.imageDetails
132+
if (imageDetails != null) {
134133
messages.add(
135134
GoogleCompletionContent(
136135
listOf(
137136
GoogleContentPart(
138137
null,
139138
GoogleContentPart.Blob(
140-
params.imageDetails!!.mediaType,
141-
params.imageDetails!!.data
139+
imageDetails.mediaType,
140+
imageDetails.data
142141
)
143142
),
144143
GoogleContentPart(message.prompt)
@@ -192,16 +191,17 @@ class GoogleRequestFactory : BaseRequestFactory() {
192191
return when (params.conversationType) {
193192
ConversationType.DEFAULT -> {
194193
val selectedPersona = service<PromptsSettings>().state.personas.selectedPersona
195-
return if (!selectedPersona.disabled) {
196-
service<FilteredPromptsService>().getFilteredPersonaPrompt(params.chatMode)
197-
} else {
198-
null
199-
}
194+
if (!selectedPersona.disabled) {
195+
val base = service<FilteredPromptsService>().getFilteredPersonaPrompt(params.chatMode)
196+
service<FilteredPromptsService>().applyClickableLinks(base)
197+
} else null
200198
}
201199

202-
ConversationType.FIX_COMPILE_ERRORS -> service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions
200+
ConversationType.FIX_COMPILE_ERRORS -> service<FilteredPromptsService>()
201+
.applyClickableLinks(service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions.orEmpty())
203202

204-
ConversationType.REVIEW_CHANGES -> service<PromptsSettings>().state.coreActions.reviewChanges.instructions
203+
ConversationType.REVIEW_CHANGES -> service<FilteredPromptsService>()
204+
.applyClickableLinks(service<PromptsSettings>().state.coreActions.reviewChanges.instructions.orEmpty())
205205

206206
else -> null
207207
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
1313
import ee.carlrobert.codegpt.settings.prompts.addProjectPath
1414
import ee.carlrobert.codegpt.settings.service.FeatureType
1515
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
16-
import ee.carlrobert.codegpt.conversations.message.Message
1716
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest
1817

1918
class LlamaRequestFactory : BaseRequestFactory() {
2019

2120
override fun createChatRequest(params: ChatCompletionParameters): LlamaCompletionRequest {
2221
val promptTemplate = getPromptTemplate()
23-
val systemPrompt =
22+
var systemPrompt =
2423
if (params.conversationType == ConversationType.FIX_COMPILE_ERRORS) {
2524
service<PromptsSettings>().state.coreActions.fixCompileErrors.instructions
2625
} else {
@@ -30,6 +29,7 @@ class LlamaRequestFactory : BaseRequestFactory() {
3029
).addProjectPath()
3130
}
3231
}
32+
systemPrompt = systemPrompt?.let { service<FilteredPromptsService>().applyClickableLinks(it) }
3333

3434
val prompt = promptTemplate.buildPrompt(
3535
systemPrompt,

0 commit comments

Comments
 (0)