fix: compatibility issues

This commit is contained in:
Carl-Robert Linnupuu 2026-04-03 11:06:12 +01:00
parent 1f9f1fd5b8
commit ef4991e2ea
9 changed files with 90 additions and 121 deletions

View file

@ -6,7 +6,6 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentContainer;
import com.intellij.openapi.wm.RegisterToolWindowTask;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
@ -17,6 +16,7 @@ import ee.carlrobert.codegpt.completions.ConversationType;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings;
import ee.carlrobert.codegpt.toolwindow.ProxyAIToolWindowFactory;
import ee.carlrobert.codegpt.toolwindow.ToolWindowInitialState;
import java.util.Arrays;
import java.util.Objects;
@ -113,13 +113,18 @@ public final class ChatToolWindowContentManager {
public @NotNull ToolWindow getToolWindow() {
var toolWindowManager = ToolWindowManager.getInstance(project);
var toolWindow = toolWindowManager.getToolWindow("ProxyAI");
// https://intellij-support.jetbrains.com/hc/en-us/community/posts/11533368171026/comments/11538403084562
return Objects.requireNonNullElseGet(toolWindow, () -> toolWindowManager
.registerToolWindow(RegisterToolWindowTask.closable(
"ProxyAI",
() -> CodeGPTBundle.get("project.label"),
Icons.DefaultSmall,
ToolWindowAnchor.RIGHT)));
if (toolWindow != null) {
return toolWindow;
}
var registeredToolWindow = toolWindowManager.registerToolWindow(
"ProxyAI",
true,
ToolWindowAnchor.RIGHT);
registeredToolWindow.setIcon(Icons.DefaultSmall);
registeredToolWindow.setStripeTitle(CodeGPTBundle.get("project.label"));
new ProxyAIToolWindowFactory().createToolWindowContent(project, registeredToolWindow);
return registeredToolWindow;
}
private Optional<Content> tryFindFirstChatTabContent() {

View file

@ -1,10 +1,11 @@
package ee.carlrobert.codegpt.diagnostics
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
@ -101,14 +102,8 @@ class ProjectDiagnosticsService(
error = "No PSI file found for: ${virtualFile.path}"
)
val rangeHighlights = DaemonCodeAnalyzerImpl.getHighlights(
document,
filter.minimumSeverity(),
project
)
val fileLevel = getFileLevelHighlights(psiFile)
val highlights = (rangeHighlights.asSequence() + fileLevel.asSequence())
val highlights = collectHighlights(document, filter.minimumSeverity())
.asSequence()
.filter { filter.includes(it.severity) }
.mapNotNull { highlight ->
extractMessage(highlight)?.let { message ->
@ -181,20 +176,22 @@ class ProjectDiagnosticsService(
}
}
private fun getFileLevelHighlights(psiFile: com.intellij.psi.PsiFile): List<HighlightInfo> {
return try {
val method = DaemonCodeAnalyzerImpl::class.java.methods.firstOrNull {
it.name == "getFileLevelHighlights" && it.parameterCount == 2
}
if (method != null) {
@Suppress("UNCHECKED_CAST")
method.invoke(null, project, psiFile) as? List<HighlightInfo> ?: emptyList()
} else {
emptyList()
}
} catch (_: Throwable) {
emptyList()
private fun collectHighlights(
document: Document,
minimumSeverity: HighlightSeverity
): List<HighlightInfo> {
val highlights = mutableListOf<HighlightInfo>()
DaemonCodeAnalyzerEx.processHighlights(
document,
project,
minimumSeverity,
0,
document.textLength
) { highlight ->
highlights.add(highlight)
true
}
return highlights
}
private fun extractMessage(info: HighlightInfo): String? {

View file

@ -3,22 +3,11 @@ package ee.carlrobert.codegpt.inlineedit
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.editor.Editor
import ee.carlrobert.codegpt.CodeGPTKeys
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
/**
* Keyboard shortcuts for Inline Edit diff feature.
*/
class AcceptAllInlineEditAction : AnAction() {
init {
val keyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER,
InputEvent.META_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK
)
shortcutSet = CustomShortcutSet(KeyboardShortcut(keyStroke, null))
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
val session =
@ -53,14 +42,6 @@ class AcceptAllInlineEditAction : AnAction() {
}
class RejectAllInlineEditAction : AnAction() {
init {
val keyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_BACK_SPACE,
InputEvent.META_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK
)
shortcutSet = CustomShortcutSet(KeyboardShortcut(keyStroke, null))
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
val session =
@ -89,11 +70,6 @@ class RejectAllInlineEditAction : AnAction() {
}
class RejectInlineEditAction : AnAction() {
init {
val keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)
shortcutSet = CustomShortcutSet(KeyboardShortcut(keyStroke, null))
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
editor.getUserData(CodeGPTKeys.EDITOR_INLINE_EDIT_RENDERER)?.rejectNext()
@ -110,14 +86,6 @@ class RejectInlineEditAction : AnAction() {
}
class AcceptCurrentInlineEditAction : AnAction() {
init {
val keyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER,
InputEvent.META_DOWN_MASK
)
shortcutSet = CustomShortcutSet(KeyboardShortcut(keyStroke, null))
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
editor.getUserData(CodeGPTKeys.EDITOR_INLINE_EDIT_RENDERER)?.acceptNext()
@ -134,14 +102,6 @@ class AcceptCurrentInlineEditAction : AnAction() {
}
class RejectCurrentInlineEditAction : AnAction() {
init {
val keyStroke = KeyStroke.getKeyStroke(
KeyEvent.VK_BACK_SPACE,
InputEvent.META_DOWN_MASK
)
shortcutSet = CustomShortcutSet(KeyboardShortcut(keyStroke, null))
}
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
editor.getUserData(CodeGPTKeys.EDITOR_INLINE_EDIT_RENDERER)?.rejectNext()

View file

@ -7,26 +7,14 @@ import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerListener
import ee.carlrobert.codegpt.conversations.Conversation
import ee.carlrobert.codegpt.settings.configuration.ChatMode
import ee.carlrobert.codegpt.toolwindow.agent.AgentToolWindowPanel
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowPanel
import ee.carlrobert.codegpt.toolwindow.history.ChatHistoryToolWindow
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails
import javax.swing.JComponent
data class ToolWindowInitialState @JvmOverloads constructor(
val conversation: Conversation,
val tags: List<TagDetails> = emptyList(),
val chatMode: ChatMode? = null,
)
class ProxyAIToolWindowFactory : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(
project: Project,
toolWindow: ToolWindow
) {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val agentToolWindowPanel = AgentToolWindowPanel(project).also {
Disposer.register(toolWindow.disposable, it)
}
@ -51,8 +39,7 @@ class ProxyAIToolWindowFactory : ToolWindowFactory, DumbAware {
}
private fun addContent(toolWindow: ToolWindow, panel: JComponent, displayName: String) {
toolWindow.contentManager.let {
it.addContent(it.factory.createContent(panel, displayName, false))
}
val contentManager = toolWindow.contentManager
contentManager.addContent(contentManager.factory.createContent(panel, displayName, false))
}
}
}

View file

@ -0,0 +1,11 @@
package ee.carlrobert.codegpt.toolwindow
import ee.carlrobert.codegpt.conversations.Conversation
import ee.carlrobert.codegpt.settings.configuration.ChatMode
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails
data class ToolWindowInitialState @JvmOverloads constructor(
val conversation: Conversation,
val tags: List<TagDetails> = emptyList(),
val chatMode: ChatMode? = null,
)

View file

@ -1,17 +1,16 @@
package ee.carlrobert.codegpt.toolwindow.chat.editor.header
import com.intellij.codeInsight.documentation.DocumentationHintEditorPane
import com.intellij.lang.documentation.DocumentationImageResolver
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.ui.popup.PopupFactoryImpl
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.ui.components.JBScrollPane
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import ee.carlrobert.codegpt.CodeGPTBundle
import java.awt.Image
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.JComponent
import javax.swing.JEditorPane
import javax.swing.Timer
class ErrorPopoverHandler(
@ -54,24 +53,23 @@ class ErrorPopoverHandler(
if (errorContent == null) return
if (errorPopup?.isVisible == true) return
val documentationHint = DocumentationHintEditorPane(
project,
emptyMap(),
object : DocumentationImageResolver {
override fun resolveImage(url: String): Image? = null
}
).apply {
setText(errorContent)
val contentPane = JEditorPane("text/html", formatContent(errorContent)).apply {
isEditable = false
isOpaque = true
border = JBUI.Borders.emptyTop(10)
foreground = UIUtil.getToolTipForeground()
background = UIUtil.getToolTipActionBackground()
font = UIUtil.getToolTipFont()
putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true)
}
val scrollPane = JBScrollPane(contentPane).apply {
border = JBUI.Borders.empty()
viewport.background = UIUtil.getToolTipActionBackground()
background = UIUtil.getToolTipActionBackground()
}
val popup = PopupFactoryImpl.getInstance()
.createComponentPopupBuilder(documentationHint, null)
val popup = JBPopupFactory.getInstance()
.createComponentPopupBuilder(scrollPane, null)
.setRequestFocus(false)
.setResizable(true)
.setMovable(true)
@ -80,9 +78,7 @@ class ErrorPopoverHandler(
.setCancelOnClickOutside(true)
.createPopup()
documentationHint.setHint(popup)
documentationHint.addMouseListener(object : MouseAdapter() {
val popupHoverListener = object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
errorLabel.putClientProperty("popupMouseInside", true)
}
@ -91,9 +87,33 @@ class ErrorPopoverHandler(
errorLabel.putClientProperty("popupMouseInside", false)
schedulePopupCloseIfNeeded()
}
})
}
contentPane.addMouseListener(popupHoverListener)
scrollPane.addMouseListener(popupHoverListener)
errorPopup = popup
popup.showUnderneathOf(errorLabel)
}
private fun formatContent(content: String): String {
return if (content.trimStart().startsWith("<html", ignoreCase = true)) {
content
} else {
val escaped = buildString(content.length) {
content.forEach { ch ->
append(
when (ch) {
'<' -> "&lt;"
'>' -> "&gt;"
'&' -> "&amp;"
'"' -> "&quot;"
else -> ch
}
)
}
}.replace("\n", "<br/>")
"<html><body>$escaped</body></html>"
}
}
}

View file

@ -4,7 +4,6 @@ import com.intellij.codeInsight.completion.PrioritizedLookupElement
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.openapi.util.TextRange
import com.intellij.psi.codeStyle.NameUtil
import com.intellij.codeInsight.lookup.LookupElementRenderer
@ -19,7 +18,7 @@ abstract class AbstractLookupItem : LookupItem {
presentation: LookupElementPresentation
) {
setPresentation(element, presentation)
highlightMatches(presentation, searchText)
emphasizeMatch(presentation, searchText)
}
})
.apply {
@ -28,7 +27,7 @@ abstract class AbstractLookupItem : LookupItem {
return PrioritizedLookupElement.withPriority(lookupElement, 1.0)
}
private fun highlightMatches(
private fun emphasizeMatch(
presentation: LookupElementPresentation,
searchText: String
) {
@ -38,14 +37,9 @@ abstract class AbstractLookupItem : LookupItem {
}
val matcher = NameUtil.buildMatcher("*$normalizedSearchText").build()
matcher.matchingFragments(displayName)
?.asReversed()
?.forEach { range: TextRange ->
presentation.decorateItemTextRange(
range,
LookupElementPresentation.LookupItemDecoration.HIGHLIGHT_MATCHED
)
}
if (matcher.matchingFragments(displayName) != null) {
presentation.isItemTextBold = true
}
}
abstract fun getLookupString(): String

View file

@ -1,4 +0,0 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
</extensions>
</idea-plugin>

View file

@ -7,7 +7,6 @@
<depends optional="true" config-file="plugin-kotlin.xml">org.jetbrains.kotlin</depends>
<depends optional="true" config-file="plugin-java.xml">com.intellij.modules.java</depends>
<depends optional="true" config-file="plugin-javascript.xml">JavaScript</depends>
<depends optional="true" config-file="plugin-cpp.xml">com.intellij.modules.cpp</depends>
<!-- <depends optional="true" config-file="plugin-go.xml">org.jetbrains.plugins.go</depends>-->
<!-- <depends optional="true" config-file="plugin-ruby.xml">com.intellij.modules.ruby</depends>-->
<!-- <depends optional="true" config-file="plugin-php.xml">com.jetbrains.php</depends>-->