diff --git a/Common/src/main/java/com/tabnineCommon/chat/actions/AskChatAction.kt b/Common/src/main/java/com/tabnineCommon/chat/actions/AskChatAction.kt
index 32648263a..75e957382 100644
--- a/Common/src/main/java/com/tabnineCommon/chat/actions/AskChatAction.kt
+++ b/Common/src/main/java/com/tabnineCommon/chat/actions/AskChatAction.kt
@@ -4,15 +4,10 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
-import com.intellij.openapi.wm.ToolWindowManager
-import com.tabnineCommon.chat.ChatBrowser
import com.tabnineCommon.chat.ChatEnabled
import com.tabnineCommon.chat.Consts.CHAT_ICON
-import com.tabnineCommon.chat.Consts.CHAT_TOOL_WINDOW_ID
-import com.tabnineCommon.general.DependencyContainer
-import org.jetbrains.concurrency.runAsync
+import com.tabnineCommon.chat.actions.common.ChatActionCommunicator
data class AskChatPayload(private val input: String)
@@ -35,49 +30,12 @@ class AskChatAction private constructor() : AnAction("Ask Tabnine", "Ask tabnine
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
- val browser = getBrowser(project) ?: return
- val ourToolWindow = ToolWindowManager.getInstance(project)
- .getToolWindow(CHAT_TOOL_WINDOW_ID) ?: return
-
val result =
Messages.showInputDialog("What do you have in mind?", "Ask Tabnine", CHAT_ICON)
.takeUnless { it.isNullOrBlank() }
?: return
- if (browser.isLoaded()) {
- ourToolWindow.activate {
- submitMessageToChat(project, result)
- }
- } else {
- browser.registerBrowserLoadedListener(ID) {
- runAsync {
- Thread.sleep(1000)
- submitMessageToChat(project, result)
- }
- }
- ourToolWindow.activate(null)
- }
- }
-
- private fun submitMessageToChat(project: Project, result: String) {
- sendMessage(project, TabnineActionRequest("submit-message", AskChatPayload(result)))
- }
-
- private fun sendMessage(project: Project, message: TabnineActionRequest) {
- val browser = getBrowser(project) ?: return
- val messageJson = DependencyContainer.instanceOfGson().toJson(message)
-
- Logger.getInstance(javaClass).info("Sending message: $messageJson")
- browser.jbCefBrowser.cefBrowser.executeJavaScript("window.postMessage($messageJson, '*')", "", 0)
- }
-
- private fun getBrowser(project: Project): ChatBrowser? {
- val browser = ChatBrowser.getInstance(project)
- if (browser == null) {
- Logger.getInstance(javaClass).warn("Browser not found on project ${project.name}")
- return null
- }
- return browser
+ ChatActionCommunicator.sendMessageToChat(project, ID, result)
}
override fun update(e: AnActionEvent) {
diff --git a/Common/src/main/java/com/tabnineCommon/chat/actions/TabnineQuickFixAction.kt b/Common/src/main/java/com/tabnineCommon/chat/actions/TabnineQuickFixAction.kt
new file mode 100644
index 000000000..4e66bf4b9
--- /dev/null
+++ b/Common/src/main/java/com/tabnineCommon/chat/actions/TabnineQuickFixAction.kt
@@ -0,0 +1,106 @@
+package com.tabnineCommon.chat.actions
+
+import com.intellij.application.subscribe
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
+import com.intellij.lang.annotation.HighlightSeverity
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Iconable
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElement
+import com.intellij.util.Processor
+import com.intellij.util.messages.Topic
+import com.tabnineCommon.chat.ChatEnabled
+import com.tabnineCommon.chat.actions.common.ChatActionCommunicator
+import com.tabnineCommon.general.StaticConfig
+import javax.swing.Icon
+
+class TabnineQuickFixAction : PsiElementBaseIntentionAction(), IntentionAction, Disposable, Iconable {
+ // `ChatActionCommunicator.sendMessageToChat` requires an EDT thread, but the `invoke` method of `IntentionAction`
+ // is not called from the EDT thread.
+ // Also, when calling `ApplicationManager.getApplication().invokeLater` directly in the `invoke` implementation,
+ // you get this error: `Side effect not allowed: INVOKE_LATER`.
+ //
+ // To work around this issue, we use an internal topic that'll call `ApplicationManager.getApplication().invokeLater`
+ // in it's own context (thread), circumventing the issue.
+ private val workaroundTopic = Topic.create("TabnineQuickFixAction", WorkaroundHandler::class.java)
+
+ companion object {
+ private const val FAMILY_NAME: String = "Fix using Tabnine"
+ private const val ID: String = "com.tabnine.chat.actions.TabnineQuickFixAction"
+ }
+
+ init {
+ workaroundTopic.subscribe(
+ this,
+ object : WorkaroundHandler {
+ override fun handle(project: Project, value: String) {
+ ApplicationManager.getApplication().invokeLater {
+ ChatActionCommunicator.sendMessageToChat(project, ID, value)
+ }
+ }
+ }
+ )
+ }
+
+ override fun getText(): String {
+ return FAMILY_NAME
+ }
+
+ override fun getFamilyName(): String {
+ return FAMILY_NAME
+ }
+
+ override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
+ if (!ChatEnabled.getInstance().enabled) return false
+
+ if (editor == null) return false
+ val textRangeToSearch = getSelectedRange(editor) ?: element.textRange
+ var foundFixes = false
+ DaemonCodeAnalyzerImpl.processHighlights(
+ editor.document,
+ project,
+ HighlightSeverity.WARNING,
+ textRangeToSearch.startOffset,
+ textRangeToSearch.endOffset,
+ Processor {
+ foundFixes = true
+ return@Processor false
+ }
+ )
+
+ return foundFixes
+ }
+
+ override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
+ ApplicationManager
+ .getApplication()
+ .messageBus
+ .syncPublisher(workaroundTopic)
+ .handle(project, "/fix-code")
+ }
+
+ private fun getSelectedRange(editor: Editor): TextRange? {
+ val selectionModel = editor.selectionModel
+ return if (selectionModel.hasSelection()) {
+ TextRange(selectionModel.selectionStart, selectionModel.selectionEnd)
+ } else {
+ null
+ }
+ }
+
+ override fun dispose() {
+ }
+
+ override fun getIcon(flags: Int): Icon {
+ return StaticConfig.getTabnineIcon()
+ }
+}
+
+interface WorkaroundHandler {
+ fun handle(project: Project, value: String)
+}
diff --git a/Common/src/main/java/com/tabnineCommon/chat/actions/common/ChatActionCommunicator.kt b/Common/src/main/java/com/tabnineCommon/chat/actions/common/ChatActionCommunicator.kt
new file mode 100644
index 000000000..647a8babd
--- /dev/null
+++ b/Common/src/main/java/com/tabnineCommon/chat/actions/common/ChatActionCommunicator.kt
@@ -0,0 +1,54 @@
+package com.tabnineCommon.chat.actions.common
+
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.wm.ToolWindowManager
+import com.tabnineCommon.chat.ChatBrowser
+import com.tabnineCommon.chat.Consts
+import com.tabnineCommon.chat.actions.AskChatPayload
+import com.tabnineCommon.chat.actions.TabnineActionRequest
+import com.tabnineCommon.general.DependencyContainer
+import org.jetbrains.concurrency.runAsync
+
+object ChatActionCommunicator {
+ fun sendMessageToChat(project: Project, actionId: String, value: String) {
+ val browser = getBrowser(project) ?: return
+ val ourToolWindow = ToolWindowManager.getInstance(project)
+ .getToolWindow(Consts.CHAT_TOOL_WINDOW_ID) ?: return
+
+ if (browser.isLoaded()) {
+ ourToolWindow.activate {
+ submitMessageToChat(project, value)
+ }
+ } else {
+ browser.registerBrowserLoadedListener(actionId) {
+ runAsync {
+ Thread.sleep(1000)
+ submitMessageToChat(project, value)
+ }
+ }
+ ourToolWindow.activate(null)
+ }
+ }
+
+ private fun submitMessageToChat(project: Project, result: String) {
+ sendMessage(project, TabnineActionRequest("submit-message", AskChatPayload(result)))
+ }
+
+ private fun sendMessage(project: Project, message: TabnineActionRequest) {
+ val browser = getBrowser(project) ?: return
+ val messageJson = DependencyContainer.instanceOfGson().toJson(message)
+
+ Logger.getInstance(javaClass).info("Sending message: $messageJson")
+ browser.jbCefBrowser.cefBrowser.executeJavaScript("window.postMessage($messageJson, '*')", "", 0)
+ }
+
+ private fun getBrowser(project: Project): ChatBrowser? {
+ val browser = ChatBrowser.getInstance(project)
+ if (browser == null) {
+ Logger.getInstance(javaClass).warn("Browser not found on project ${project.name}")
+ return null
+ }
+ return browser
+ }
+}
diff --git a/Common/src/main/resources/META-INF/common-plugin.xml b/Common/src/main/resources/META-INF/common-plugin.xml
index 75e524003..6b03f7917 100644
--- a/Common/src/main/resources/META-INF/common-plugin.xml
+++ b/Common/src/main/resources/META-INF/common-plugin.xml
@@ -121,6 +121,10 @@
id="com.tabnineCommon.userSettings.AppSettingsConfigurable" displayName="Tabnine"/>
+
+ com.tabnineCommon.chat.actions.TabnineQuickFixAction
+ Tabnine intentions
+
diff --git a/Common/src/main/resources/intentionDescriptions/TabnineQuickFixAction/description.html b/Common/src/main/resources/intentionDescriptions/TabnineQuickFixAction/description.html
new file mode 100644
index 000000000..714c81702
--- /dev/null
+++ b/Common/src/main/resources/intentionDescriptions/TabnineQuickFixAction/description.html
@@ -0,0 +1,5 @@
+
+
+Use Tabnine to fix the selected code
+
+
\ No newline at end of file